package com.popcap.util;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.io.FileWriter;
import java.io.BufferedWriter;


import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * GameDescriptorHandler
 * 
 * Description: A parser to convert a game's XML descriptor to binary resource
 * files. Descriptor and output files formats are described in the doc folder.
 * <br/>
 * 
 * @author Plamen Dragozov dragozov@yahoo.com
 *  
 */
public class GameDescriptorHandler extends DefaultHandler
{
	
	private static final String ID_PREFIX_SOUND = "    static final int SOUND_ID_";
	private static final String ID_PREFIX_SOUND_MAX = "    static final int MAX_SOUND_ID = ";
	private static final String ID_PREFIX_SOUND_INDEX = "    static final int SOUND_INDEX_";
	private static final String CONSTANTS_SOUND_FORMAT = "    static final String SOUND_FORMAT = \"\"";
	private static final String CONSTANTS_IMAGE_FORMAT = "    static final String IMAGE_FORMAT = \"\"";
	private static final String CONSTANTS_IMAGE_TRANSFORM = "    static final String IMAGE_TRANSFORM = \"\"";
	private static final String CONSTANT_PREFIX_AUDIO_FORMAT = "AudioConstants.AUDIO_FORMAT_";
	private static final String CONSTANT_PREFIX_IMAGE_FORMAT = "ImageConstants.IMAGE_FORMAT_";
	static final String ID_PREFIX_IMAGE = "    static final int IMAGE_ID_";
	static final String IMAGE_ID = "IMAGE_ID_";
	private static final String ID_PREFIX_IMAGE_MAX = "    static final int MAX_IMAGE_ID = ";
	private static final String ID_PREFIX_IMAGE_SIZE_MAX = "    static final int MAX_IMAGE_SIZE = ";
	private static final String ID_PREFIX_PALETTE = "    static final int PALETTE_ID_";
	private static final String PREFIX_EXPLICIT_CONSTANT = "    static final ";
	private static final String PREFIX_IMAGE_SET_ID = "    static final int IMAGE_SET_ID_";
	private static final String CONSTANTS_KEY_MAPPING_CODES = "    static final String KEY_MAPPING_CODES = \"\"";
	private static final String CONSTANTS_KEY_MAPPING_ACTIONS = "    static final String KEY_MAPPING_ACTIONS = \"\"";
	private static final String CONSTANTS_KEY_MAPPING_ACTION_PREFIX = "InputConstants.KEY_INDEX_";

	//PJK - Added layouts
	private static String PREFIX_LAYOUT = "	static final String LAYOUT_";

	//PJK - Added vectors
	private static String PREFIX_VECTOR = "	static final String VECTOR_";

	private static String PREFIX_SEQUENCE = "    static final String SEQUENCE_";

	private static final String CONSTANTS_INTERFACE_PALETTE_FILE_SIZE = "    static final int _PALETTE_SIZE= ";
	//The starting of the interface
	private static final String CONSTANTS_INTERFACE_PREFIX = "/**\n" + "* %NAME%\n" + "*\n" + "* Description: This interface is automatically generated\n" + "* from the resource building tools and represents the resource\n" + "* addressing in the last built resource files.\n" + "* <br/>\n" + "* As all the constants get inlined, the class itself gets removed by\n" + "* the optimization routines in the building script and doesn't \n" + "* increase the final JAR size!\n" + "* <br/> \n" + "* DO NOT EDIT MANUALLY AS ALL YOUR CHANGES WILL BE OVERWRITEN NEXT\n" + "* TIME THE RESOURCES GET CREATED!\n" + "* <br/>\n" + "* \n" + "* @author Plamen Dragozov plamen.dragozov@tko-software.com\n" + "*\n" + "*/\n" + "interface %NAME%\n" + "{\n";

	//@iant used for generateing brew Constants class
	private static final String CONSTANTS_BREW_PREFIX = "/**\n" + "* %NAME%\n" + "*\n" + "* Description: This class is automatically generated\n" 
		+ "* from the resource building tools and represents the resource\n" + "* addressing in the last built resource files.\n" 
		+ "* <br/>\n" 
		+ "* DO NOT EDIT MANUALLY AS ALL YOUR CHANGES WILL BE OVERWRITEN NEXT\n" 
		+ "* TIME THE RESOURCES GET CREATED!\n" + "* <br/>\n" + "* \n" + "* @author Ian Tierney itierney@popcap.com\n" 
		+ "*\n" + "*/\n" ;//+ "interface %NAME%\n" + "{\n";

	private static final String KEY_MAPPING_DEFAULTS = "49:UP_LEFT,50:UP,51:UP_RIGHT,52:LEFT,53:FIRE,54:RIGHT,55:DOWN_LEFT,56:DOWN,57:DOWN_RIGHT";

	// Sound index types
	private static final int SOUND_INDEX_NORMAL = 0;
	private static final int SOUND_INDEX_MIDI_STRIP = 1;
	private static final int SOUND_INDEX_RESOURCE = 2;

	// Temp filename for mergeing
	private static final String MERGED_MIDI_NAME = "merged.mid";

	/**
	 * The element handling methods always take a single Attributes parameter
	 */
	private static final Class[] PARAM_TYPES =
	{Attributes.class};

	/**
	 * The parameters list for the element handling methods.
	 */
	private static final Attributes[] PARAMS = new Attributes[1];
	
	/**
	 * The element handling methods always take a single Attributes parameter
	 */
	private static final Class[] EMPTY_PARAM_TYPES =
	{};

	/**
	 * The parameters list for the element handling methods.
	 */
	private static final Object[] EMPTY_PARAMS = new Object[0];


	// These also appear in ImageConstants.java
	static final int IMAGE_TRANSFORM_NONE = 0;
	static final int IMAGE_TRANSFORM_FLIP_HORIZONTAL = 1 << 0;
	static final int IMAGE_TRANSFORM_FLIP_VERTICAL = 1 << 1;
	static final int IMAGE_TRANSFORM_PALETTE_SHIFT = 1 << 2;
	static final int IMAGE_TRANSFORM_ROTATE_90 = 1 << 3;

	static final String IMAGE_TRANSFORM_NAME_NONE = "none";
	static final String IMAGE_TRANSFORM_NAME_FLIP_HORIZONTAL = "hflip";
	static final String IMAGE_TRANSFORM_NAME_FLIP_VERTICAL = "vflip";
	static final String IMAGE_TRANSFORM_NAME_PALETTE_SHIFT = "palette";
	static final String IMAGE_TRANSFORM_NAME_ROTATE = "rotate";

	protected static final StringBuffer CONSTANTS_INTERFACE = new StringBuffer(5120);

	protected static final Boolean FALSE = new Boolean(false);
	protected static final Boolean TRUE = new Boolean(true);

	//hold the paths to properties files for the different types of resources
	// mapped by the resource type (for example for every different locale there
	// could be a different text properties file)
	protected ArrayMap textTypes = new ArrayMap();
	protected ArrayMap textConfigurations = new ArrayMap();
	protected TextConfigurationDescriptor textConfiguration;
	protected ArrayList soundAttributes;
	protected Map imageTypes;

	//holds all transformed images representations mapped by their parent
	protected Map m_transformedImages;
	//holds data for the image sets mapped to their ids
	Map m_images;

	protected int m_nImageCount = 0;
	protected int m_nMaxImageSize = 0;

	//the most recently created transformed image
	protected LinkedHashMap currentTransformedImage;
	//	the most recently created image
	protected Map currentImage;

	//Holds the indexes for all texts
	public ArrayMap textsMap;
	protected int m_nGlobalTextCount;

	//Map of all sequences mapped to their ids
	protected List sequencesList;

	//list of all configured palettes; by rgb to rgb
	protected List palettesList;
	//list of all configured palettes; by index to rgb
	//protected List palettesIndexList;
	//the index of each palette mapped by it's name
	protected Map paletteIdMap;

	//Maps all the sound names to an item in the sounds list
	protected ArrayMap soundMap;

	//Maps all the sound paths to their data
	protected ArrayMap soundDataMap;

	//Holds the type constants of audio formats
	protected ArrayMap audioFormatMap;

	//Layouts
	protected List layoutsList;

	//Vectors
	protected List vectorList;

	List explicitConstants;

	protected int paletteFileSize;

	//Holds the sound descriptors
	protected SoundDescriptor currentSoundDes;

	// Holds the image formats
	public ArrayMap imageDescriptors = new ArrayMap();

	// Holds the device-specific key mappings
	protected ArrayMap m_KeyMappings = new ArrayMap();

	protected Map m_bitmapFonts = new HashMap();
	protected Map m_systemFonts = new HashMap();

	public List m_FontIds = new ArrayList();

	static String m_fontData;
	static Map m_fontAttributes = new HashMap();

	static String m_SoftKeysFontId;
	static int m_nSoftKeysFont;
	static String  m_nTarget;

	static final String FONT_ATTRIBUTES_PREFIX = "##";
	static final String FONT_ATTRIBUTES_HEIGHT = "FONT_HEIGHT";
	static final String FONT_ATTRIBUTES_HASHING = "FONT_HASH";
	static final String FONT_ATTRIBUTES_UPPER_CASE = "UPPER_CASE_ONLY";

	final static int SYSTEM_FONT_SIZE_SMALL = 1;
	final static int SYSTEM_FONT_SIZE_MEDIUM = 2;
	final static int SYSTEM_FONT_SIZE_LARGE = 4;

	final static int SYSTEM_FONT_FACE_SYSTEM = 1;
	final static int SYSTEM_FONT_FACE_MONOSPACE = 2;
	final static int SYSTEM_FONT_FACE_PRPOPRTIONAL = 4;

	final static int SYSTEM_FONT_STYLE_PLAIN = 1;
	final static int SYSTEM_FONT_STYLE_BOLD = 2;
	final static int SYSTEM_FONT_STYLE_ITALIC = 4;
	final static int SYSTEM_FONT_STYLE_UNDERLINED = 8;

	final static String ID_PREFIX_FONT_ID = "    static final int FONT_ID_";
	final static String ID_PREFIX_SOFT_KEYS_FONT_ID = "    static final int SOFT_KEYS_FONT_ID = ";
	
	String m_CurrentSetGroup = null;
	List m_SetGroupIds = null;

	StringBuffer s1 = new StringBuffer();
	StringBuffer s2 = new StringBuffer();
	StringBuffer s3 = new StringBuffer();



	// BDOYLE 29 May 2007 BREW RELATED VARS for parsing and creating the fontdata.cpp and fontdata.h 
	static int fontData[][] = new int[10][4];
	static int hashTableOffset[][][] = new int[10][50][3];
	static int hashTableImageData[][][] = new int[10][300][3];
	static int sequenceCount[] = new int[10]; 
	static int numCharsPerFont[] = new int[10];
	static int fontHeight[] = new int[10];
	static int upperCaseOnly[] = new int[10];
	char charLastIteration = '0';
	static int m_nNumFontsParsed=0;
	static int m_nNumberOfFonts=0;

	static FileOutputStream brewResStream = null;
	static StringBuffer brewStrBuff = null;

	
	/**
	 *  
	 */
	public GameDescriptorHandler()
	{
		soundAttributes = new ArrayList();
		imageTypes = new LinkedHashMap();
		m_transformedImages = new LinkedHashMap();
		m_images = new LinkedHashMap();//only used for sets(scene) ids
		soundMap = new ArrayMap();
		soundDataMap = new ArrayMap();
		audioFormatMap = new ArrayMap();
		palettesList = new ArrayList();
		//palettesIndexList = new ArrayList();
		paletteIdMap = new LinkedHashMap();
		sequencesList = new ArrayList();
		explicitConstants = new ArrayList();
		textsMap = new ArrayMap();

		layoutsList = new ArrayList();
		vectorList = new ArrayList();
		
		m_SetGroupIds = new ArrayList();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String,
	 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
	 */
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
	{

		try
		{

			
			//for every tag that is supported there is a publlic method named
			// on_"tag_name"
			String methodName = "on_" + qName.replace('-', '_');
			//get the handling method or the current element
			Method method = this.getClass().getMethod(methodName, PARAM_TYPES);

			PARAMS[0] = attributes;
			method.invoke(this, (Object[])PARAMS);
		

		}
		catch (NoSuchMethodException e)
		{
			System.out.println("*No handler for <" + qName + ">.");
		}
		catch (Exception e)
		{
			//System.out.println( "PARAMS " +  PARAMS.toString() );
			e.printStackTrace();
		}
	}
	public void endElement(String uri, String localName, String qName) throws SAXException
	{
		try
		{
			
			//for every tag that is supported there is a publlic method named
			// on_"tag_name"
			String methodName = "after_" + qName.replace('-', '_');
			//get the handling method or the current element
			Method method = this.getClass().getMethod(methodName, EMPTY_PARAM_TYPES);
			//System.out.println("method name  = "+methodName);
			method.invoke(this, EMPTY_PARAMS);

		}
		catch (NoSuchMethodException e)
		{
			
			//no big deal
			//System.out.println( "PARAMS " +  PARAMS.toString() );
			//System.out.println("No handler for <" + qName + ">.");
		}
		catch (Exception e)
		{
			//System.out.println( "PARAMS " +  PARAMS.toString() );
			e.printStackTrace();
		}
	}


	//==================================================================
	//						TAG HANDLERS
	// Public methods that handle a particular tag and create an 
	// in-memory representation.
	// A very simplistic approach is used for representing the
	// configuration.
	// Only Map, List, primitive objects and their wrappers are used
	// for saving the data from the XML file.
	//==================================================================

	public void on_texts_type(Attributes att)

	{
		String orientation = "r2l".equalsIgnoreCase(att.getValue("orientation")) ? "2":"1";
		textTypes.put(att.getValue("type"), new String[]{att.getValue("path"),orientation});
	}

	public void on_texts_multilingual(Attributes att)
	{
		textConfigurations.put(att.getValue("type"), new TextConfigurationDescriptor(att.getValue("global")));
	}

	public void on_texts_multilingual_item(Attributes att)
	{
		TextConfigurationDescriptor config = (TextConfigurationDescriptor)textConfigurations.getValue(textConfigurations.size() - 1);
		config.addLanguage(att.getValue("id"), att.getValue("label"));
	}

	public void on_bitmap_font(Attributes att)
	{
		m_bitmapFonts.put(att.getValue("id"), new String[]{att.getValue("imageId"), att.getValue("dataFile")});
	}

	public void on_system_font(Attributes att)
	{
		String value = att.getValue("size");
		int size = SYSTEM_FONT_SIZE_MEDIUM;
		if(value != null)
		{
			value = value.toLowerCase();
		}
		if("large".equals(value))
		{
			size = SYSTEM_FONT_SIZE_LARGE;
		}
		else if("small".equals(value))
		{
			size = SYSTEM_FONT_SIZE_SMALL;
		}

		value = att.getValue("face");
		int face = SYSTEM_FONT_FACE_SYSTEM;
		if(value != null)
		{
			value = value.toLowerCase();
		}
		if("monospace".equals(value))
		{
			face = SYSTEM_FONT_FACE_MONOSPACE;
		}
		else if("proportional".equals(value))
		{
			face = SYSTEM_FONT_FACE_PRPOPRTIONAL;
		}

		int style = 0;
		value = att.getValue("bold");
		if(value != null)
		{
			value = value.toLowerCase();
		}
		if("yes".equals(value) || "true".equals(value))
		{
			style |= SYSTEM_FONT_STYLE_BOLD;
		}

		value = att.getValue("italic");
		if(value != null)
		{
			value = value.toLowerCase();
		}
		if("yes".equals(value) || "true".equals(value))
		{
			style |= SYSTEM_FONT_STYLE_ITALIC;
		}

		value = att.getValue("underlined");
		if(value != null)
		{
			value = value.toLowerCase();
		}
		if("yes".equals(value) || "true".equals(value))
		{
			style |= SYSTEM_FONT_STYLE_UNDERLINED;
		}

		if(style == 0)
		{
			style = SYSTEM_FONT_STYLE_PLAIN;
		}

		int color = -1;
		value = att.getValue("color");
		if(value == null)
		{
			value = att.getValue("colour");
		}
		if(value != null && value.length() > 0)
		{
			color = Integer.parseInt(value, 16);
		}

		m_systemFonts.put(att.getValue("id"), new int[]	{size, face, style, color});
	}

	public void on_soft_keys_font(Attributes att)
	{
		m_SoftKeysFontId = att.getValue("id");
	}

	public void on_sounds_type(Attributes att)
	{
		soundAttributes.add(new SoundTypeDescriptor(att.getValue("device"), att.getValue("format"), att.getValue("path")));
	}

	public void on_sound(Attributes att)
	{
		currentSoundDes = new SoundDescriptor();
		currentImage = null;
		currentTransformedImage = null;

		soundMap.put(att.getValue("id"), currentSoundDes);
	}

	public void on_images_type(Attributes att)
	{
		imageTypes.put(att.getValue("type"), att.getValue("path"));
	}

	public void on_transformed_image(Attributes att)
	{
		currentImage = null;
		currentTransformedImage = new LinkedHashMap();
		currentTransformedImage.put("id", att.getValue("id"));
		String parentId = att.getValue("parentId");
		ArrayList transforms = (ArrayList)m_transformedImages.get(parentId);
		if(transforms == null)
		{
			transforms = new ArrayList();
			m_transformedImages.put(parentId, transforms);
		}
		transforms.add(currentTransformedImage);
		if(m_CurrentSetGroup != null)
		{
			List imageSets = (List)currentTransformedImage.get("set");
			if(imageSets == null)
			{
				imageSets = new ArrayList();
				currentTransformedImage.put("set", imageSets);
			}
			imageSets.add(m_CurrentSetGroup);
		}
	}

	public void on_image(Attributes att)
	{
		currentTransformedImage = null;
		currentImage = new LinkedHashMap();
		String id = att.getValue("id");
		currentImage.put("id", id);
		m_images.put(id, currentImage);
		if(m_CurrentSetGroup != null)
		{
			List imageSets = (List)currentImage.get("set");
			if(imageSets == null)
			{
				imageSets = new ArrayList();
				currentImage.put("set", imageSets);
			}
			imageSets.add(m_CurrentSetGroup);
		}
	}

	public void on_set_group(Attributes att)
	{
		m_CurrentSetGroup = att.getValue("set");
		String[] temp = new String[]{ new String( att.getValue("id") ).toUpperCase(), new String( m_CurrentSetGroup ) };
		m_SetGroupIds.add( temp );
		temp = null;		
	}
	
	public void after_set_group()
	{
		m_CurrentSetGroup = null;
	}
	
	public void on_set(Attributes att)
	{
		List imageSets = null;
		if(currentTransformedImage != null)
		{
			imageSets = (List)currentTransformedImage.get("set");
			if(imageSets == null)
			{
				imageSets = new ArrayList();
				currentTransformedImage.put("set", imageSets);
			}
			imageSets.add(att.getValue("id"));
		}
		else if(currentImage != null)
		{
			imageSets = (List)currentImage.get("set");
			if(imageSets == null)
			{
				imageSets = new ArrayList();
				currentImage.put("set", imageSets);
			}
			imageSets.add(att.getValue("id"));
		}
		else if(currentSoundDes != null)
		{
			currentSoundDes.addSet(Integer.parseInt(att.getValue("id")));//set id	
		}
	}

	public void on_transform(Attributes att)
	{
		currentTransformedImage.put(att.getValue("type"), att.getValue("value"));
	}

	public void on_palette(Attributes att)
	{
		Map colorMap = new LinkedHashMap();
		String strId = att.getValue("", "id");
		colorMap.put("id", strId);
		Integer id = new Integer(palettesList.size());
		palettesList.add(colorMap);
		paletteIdMap.put(strId, id);
	}

	public void on_replace(Attributes att)
	{
		Map palette = (Map)palettesList.get(palettesList.size() - 1);
		int nFrom = Integer.parseInt(att.getValue("from").toLowerCase(), 16);
		int nTo = Integer.parseInt(att.getValue("to").toLowerCase(), 16);
		//		if (nFrom == nTo)
		//		{
		//			System.out.println("COLOR from/to same: " + palette.get("id") + " (" + att.getValue("from") + ")");
		//		}
		palette.put(new Integer(nFrom), new Integer(nTo));
	}

	public void on_sequence(Attributes att)
	{
		Map sequence = new LinkedHashMap();
		sequence.put("id", att.getValue("id"));
		sequence.put("type", att.getValue("type"));
		sequencesList.add(sequence);
	}

	public void on_sequence_item(Attributes att)
	{
		Map sequence = (Map)sequencesList.get(sequencesList.size() - 1);
		List items = (List)sequence.get("items");
		if(items == null)
		{
			items = new ArrayList();
			sequence.put("items", items);
		}
		items.add(att.getValue("id"));
	}

	public void on_vector_list(Attributes att)
	{
		Map sequence = new LinkedHashMap();
		sequence.put("id", att.getValue("id"));
		vectorList.add(sequence);
	}

	public void on_vector(Attributes att)
	{
		Map sequence = (Map)vectorList.get(vectorList.size() - 1);
		List items = (List)sequence.get("coords");
		if(items == null)
		{
			items = new ArrayList();
			sequence.put("coords", items);
		}
		items.add(att.getValue("x"));
		items.add(att.getValue("y"));
	}

	public void on_constant(Attributes att)
	{
		explicitConstants.add(new String[]
		{att.getValue("type"), att.getValue("name"), att.getValue("value")});
	}

	/**
	 * Layouts for sparsely placed images, eg Chuzzle UI
	 */
	public void on_layout(Attributes att)
	{
		Map layout = new LinkedHashMap();
		layout.put("id", att.getValue("id"));
		layoutsList.add(layout);
	}

	public void on_layout_item(Attributes att)
	{
		Map layout = (Map)layoutsList.get(layoutsList.size() - 1);
		List items = (List)layout.get("items");
		if(items == null)
		{
			items = new ArrayList();
			layout.put("items", items);
		}
		items.add(att.getValue("image"));
		items.add(att.getValue("x"));
		items.add(att.getValue("y"));
	}

	/**
	 * Utility method for reading an integer attribute.
	 * @param att
	 * @param name
	 * @param defaultValue
	 * @return
	 */
	protected static final Integer getInteger(Attributes att, String name, int defaultValue)
	{
		String value = att.getValue(name);
		if(value != null)
		{
			return new Integer(value);
		}
		else
		{
			return new Integer(defaultValue);
		}
	}

	/**
	 * Utility method for reading a boolean attribute.
	 * @param att
	 * @param name
	 * @return
	 */
	protected static final Boolean getBoolean(Attributes att, String name)
	{
		String value = att.getValue(name);
		if("true".equals(value))
		{
			return TRUE;
		}
		else
		{
			return FALSE;
		}
	}

	//==================================================================
	//						DATA WRITERS
	//
	//	Methods for writing binary resources using the parsed data.
	//==================================================================

	/**
	 * Writes the parsed data to binary data files and saves the resources indexes
	 * to an interface of constants.
	 * 
	 * @param device
	 * @param symbols
	 * @param deviceProperties
	 * @param textsType
	 * @param imagesOut
	 * @param usePNGDecompressor
	 * @param soundsOut
	 * @param palettesOut
	 * @param resourceDir
	 * @param utilDir
	 * @throws Exception
	 *             In case the config file has problems or the writing failed
	 */
	public void writeResources(String device, ArrayList symbols, Properties deviceProperties, String textsType, OutputStream imagesOut, boolean usePNGDecompressor, boolean forcePNGDecompressor, OutputStream soundsOut, OutputStream palettesOut, OutputStream fontsOut, String resourcesDir, String utilDir, String target) throws Exception
	{
		m_nTarget = target;
		/*if("BREW".equalsIgnoreCase(m_nTarget))
		{
			resourcesDir += File.separator+"brew-res";
		}*/
		if(writeGameResources(device, symbols, deviceProperties, textsType, imagesOut, usePNGDecompressor, forcePNGDecompressor, soundsOut, palettesOut, fontsOut, resourcesDir, utilDir, target))
		{
			// Acquire device properties
			ArrayList audioFormats = parseCommaSeparatedList(deviceProperties.getProperty("device.audioFormats"));
			ArrayList audioStrips = parseCommaSeparatedList(deviceProperties.getProperty("device.audioStrips"));
			parseCommaSeparatedMap(m_KeyMappings, deviceProperties.getProperty("device.keymapping"));
			parseCommaSeparatedMap(m_KeyMappings, KEY_MAPPING_DEFAULTS); // add defaults; ignored if already mapped
			String strSoundsSeparate = deviceProperties.getProperty("device.soundsSeparate");
			boolean bSoundsSeparate = strSoundsSeparate != null && strSoundsSeparate.trim().compareToIgnoreCase("TRUE") == 0;
			boolean bPlainText = symbols.indexOf("PLAIN_TEXT") != -1;			
			String strSoundResourcePrefix = null;
			if(bSoundsSeparate)
			{
				strSoundResourcePrefix = deviceProperties.getProperty("device.soundResourcePrefix");
				if(strSoundResourcePrefix == null)
				{
					strSoundResourcePrefix = "";
				}
			}

			// Remove key mappings that evaluate to 'none'
			for(int i = m_KeyMappings.size() - 1;i >= 0;i-- )
			{
				if(((String)m_KeyMappings.getValue(i)).toUpperCase().compareToIgnoreCase("NONE") == 0)
				{
					m_KeyMappings.remove(i);
				}
			}

			// Configure audio formats
			for(int i = 0;i < audioFormats.size();i++ )
			{
				audioFormatMap.put(((String)audioFormats.get(i)).toLowerCase(), new AudioFormatDescriptor());
			}
			for(int i = 0;i < audioStrips.size();i++ )
			{
				((AudioFormatDescriptor)audioFormatMap.get(((String)audioStrips.get(i)).toLowerCase())).setStrip(true);
			}

			//		System.out.println("Phone supports the following audio formats:");
			//		for (int i = 0; i < audioFormatMap.size(); i++)
			//		{
			//			AudioFormatDescriptor desc = (AudioFormatDescriptor)audioFormatMap.getValue(i);
			//			System.out.println("- " + audioFormatMap.getKey(i) + (desc.isStrip() ? " (strip)" : ""));
			//		}

			//write texts and create text ids map
			if(textsType != null && textsType.length() > 0)
			{
				writeTexts(textsType, resourcesDir, bPlainText);
			}
			else
			{
				System.out.println("Warning: No texts type or texts output path configured!");
			}

			//write sounds and create sound ids
			if(soundsOut != null)
			{
				writeSounds(soundsOut, device, utilDir, bSoundsSeparate, strSoundResourcePrefix, resourcesDir);
			}
			else
			{
				System.out.println("Warning: No sounds type or sounds output path configured!");
			}

			//write images and create image ids
			if(imagesOut != null)
			{
				writeImages(imagesOut, resourcesDir, utilDir, device, textsType, usePNGDecompressor, forcePNGDecompressor, target);
			}
			else
			{
				System.out.println("Warning: No images type or images output path configured!");
			}

			//write palettes, palletes' id map was filled while parsing the xml
			// MUST HAPPEN AFTER WRITING IMAGES since that is when colors get converted to indexes
			if(palettesOut != null)
			{
				paletteFileSize = writePalettes(palettesOut);
			}
			else
			{
				System.out.println("Warning: No palettes output path configured!");
			}

			//write fonts
			if(fontsOut != null)
			{
				writeFonts(fontsOut);
			}
			else
			{
				System.out.println("Warning: No font output path was configured!");
			}
		}
	}

	/**
	 * 
	 * @param device
	 * @param symbols
	 * @param deviceProperties
	 * @param textsType
	 * @param imagesOut
	 * @param usePNGDecompressor
	 * @param soundsOut
	 * @param palettesOut
	 * @param fontsOut
	 * @param resourcesDir
	 * @param utilDir
	 * @throws Exception
	 */
	public boolean writeGameResources(String device, ArrayList symbols, Properties deviceProperties, String textsType, OutputStream imagesOut, boolean usePNGDecompressor, boolean forcePNGDecompressor, OutputStream soundsOut, OutputStream palettesOut, OutputStream fontsOut, String resourcesDir, String utilDir, String target) throws Exception
	{
		//Let inheriting classes do something if they need
		return true;
	}

	/**
	 * Interprets a comma-separated string of and returns a representative array-list
	 * 
	 * @param formats
	 */
	protected ArrayList parseCommaSeparatedList(String formats)
	{
		ArrayList a = new ArrayList();
		if(formats != null)
		{
			String[] subStrings = formats.split(",");
			if(subStrings != null)
			{
				for(int i = 0;i < subStrings.length;i++ )
				{
					a.add(subStrings[i]);
				}
			}
		}
		return a;
	}

	protected void parseCommaSeparatedMap(ArrayMap map, String strItems)
	{
		ArrayList items = parseCommaSeparatedList(strItems);
		for(int i = 0;i < items.size();i++ )
		{
			String strItem = (String)items.get(i);
			int nSeparator = strItem.indexOf(':');
			if(nSeparator != -1)
			{
				String strKey = strItem.substring(0, nSeparator);
				String strValue = strItem.substring(nSeparator + 1);
				if( !map.containsKey(strKey))
				{
					map.put(strKey, strValue);
				}
			}
		}
	}

	//creates the brew recources file only if it hasnt been created already
	protected void CreateBrewResFile() throws Exception
	{
		/*
		File outputFolder = new File("brew-resources");
		if( !outputFolder.exists())
		{
			outputFolder.mkdir();
			System.out.println("RESOURCE HANDLER MESSAGE: Created folder " + outputFolder.getAbsolutePath());
		}
		//##
		File brcFile = new File(outputFolder, "resources.brc");
		DataOutputStream resourcesOut = new DataOutputStream(new FileOutputStream(brcFile));
		*/
		
		if("BREW".equalsIgnoreCase(m_nTarget) 
				&& brewResStream == null)
		{
			//##
			//todo @ian create resources.brc here instead of in writeImages and then write images into this
			File outputFolder = new File("brew-resources");
			if( !outputFolder.exists())
			{
				outputFolder.mkdir();
				System.out.println("RESOURCE HANDLER MESSAGE: Created folder " + outputFolder.getAbsolutePath());
				
			}
			//##
			File brcFile = new File(outputFolder, "resources.brc");
			brewResStream = new FileOutputStream(brcFile,true);//append to file
			brewStrBuff = new StringBuffer(5120);
		}
	}
	
	


	/**
	 * Writes the texts of the given type to the given stream. <br/>Saves the
	 * indices of the texts in the textsMap
	 * 
	 * @param type
	 * @param out
	 * @throws Exception
	 */
	protected void writeTexts(String strConfig, String resourcesDir, boolean bPlainText) throws Exception
	{
		// if no text configurations specified, use a generic one
		if(textConfigurations.containsKey(strConfig))
		{
			textConfiguration = (TextConfigurationDescriptor)textConfigurations.get(strConfig);
		}
		else
		{
			textConfiguration = new TextConfigurationDescriptor(strConfig);
		}
		
		CreateBrewResFile();
		ArrayMap globalMap = null;
		ArrayMap otherMap = null;
		for(int nLanguage = -1;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			// Open file
			String strFilename = resourcesDir + "t";
			if(nLanguage != -1)
			{
				strFilename += "-" + textConfiguration.getLanguage(nLanguage);
			}
			OutputStream out = new FileOutputStream(strFilename);

			// Text type
			ArrayMap languageMap = new ArrayMap();
			String type = textConfiguration.getLanguage(nLanguage);
			String[] props = (String[])textTypes.get(type);
			String path = props[0];
			String orientation = props[1];
			textConfiguration.setLanguageOrientation(nLanguage, orientation);
			if(path == null)
			{
				throw new IllegalArgumentException("Texts type '" + type + "' was not configured!");
			}
			else
			{
				byte[] UTF8Header =
				{(byte)0xEF, (byte)0xBB, (byte)0xBF};
				byte[] TextFileHeader = new byte[UTF8Header.length];
				InputStream in = new FileInputStream(path);
				in.read(TextFileHeader);

				boolean bUTF8 = true;
				for(int i = 0;i < UTF8Header.length;i++ )
				{
					if(TextFileHeader[i] != UTF8Header[i])
					{
						bUTF8 = false;
					}
				}

				if( !bUTF8)
				{
					System.out.println("*** WARNING: non-UTF-8 text file: " + path);
					in.close();
					in = new FileInputStream(path);
				}

				OrderedProperties textProps;
				if( !bUTF8 )
				{
					textProps = IOUtils.readProperties(in, "ISO-8859-1");
				}
				else
				{
					textProps = IOUtils.readProperties(in, "UTF-8");
				}
				
				in.close();
				//##				
				//put the texts into a list and preserve the indices
				
				if("BREW".equalsIgnoreCase(m_nTarget))
				{
					//brew only needs english and spanish string sets
					if(textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("eng") 
						|| textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("spa"))
						
					{
						brewStrBuff.append("RES_ID_LANG_" + textConfiguration.getLanguage(nLanguage).toUpperCase() + "\t" + 5001 + "\t");	
						brewStrBuff.append("IMAGE" + "\t" + '"' + "text/plain\\000\\000\\000" + '"' +  "\t" + '"');//we dont know what text/plain\000\000\000 does but was part of the original brew format
					}
				}
				
				for(int i = 0;i < textProps.size();i++ )
				{
					String key = textProps.getPropertyKey(i);
					languageMap.put(key, textProps.getProperty(key));
					
					if("BREW".equalsIgnoreCase(m_nTarget))
						
						if(textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("eng") 
							|| textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("spa"))
						{																								
							brewStrBuff.append(textProps.getProperty(key) );	//write the strings into.brc file
							if(i != textProps.size()-1)
								brewStrBuff.append("^");
						}
					
				}
				
				if("BREW".equalsIgnoreCase(m_nTarget))
				{
					if((textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("eng") 
						|| textConfiguration.getLanguage(nLanguage).equalsIgnoreCase("spa")))	
					{					
						brewStrBuff.append("~\"\n"); 
						//brewStrBuff.append('~'); 
						//brewStrBuff.append('"' + "\n"); 
					}
				}

				//add keys for lanaguage filenames
				if(nLanguage == -1)
				{
					for(int i = 0;i < textConfiguration.getLanguageCount();i++ )
					{
						languageMap.put("LANGUAGE_FILENAME_" + textConfiguration.getLanguage(i).toUpperCase().replace('-', '_'), textConfiguration.getLanguage(i));
					}
				}

				//write the strings resource to the output
				if(bPlainText)
				{
					//out.write(UTF8Header);
					BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, "ISO-8859-1"));
					for(int i = 0;i < languageMap.size();i++ )
					{
						bw.write((String)languageMap.getValue(i) + "\r\n");
					}
					bw.close();
				}
				else
				{
					DataOutputStream dos = new DataOutputStream(out);
					for(int i = 0;i < languageMap.size();i++ )
					{
						dos.writeUTF((String)languageMap.getValue(i));
					}
				}
			}

			out.close();

			// add items to proper reference map
			if(nLanguage == -1)
			{
				globalMap = languageMap;
			}
			else if(otherMap == null)
			{
				// first instance
				otherMap = languageMap;
			}
			else
			{
				// verify same tags
				for(int i = 0;i < languageMap.size();i++ )
				{
					if(languageMap.getKey(i).compareToIgnoreCase(otherMap.getKey(i)) != 0)
					{
						throw new Exception("*** LANGUAGES HAVE DIFFERENT TAGS: " + languageMap.getKey(i) + " vs " + otherMap.getKey(i));
					}
				}
			}
		}
		
		if("BREW".equalsIgnoreCase(m_nTarget))
		{					 
			brewResStream.write(brewStrBuff.toString().getBytes());	
		}

		// compose ids of globals, then rest
		for(int i = 0;i < globalMap.size();i++ )
		{
			textsMap.put(globalMap.getKey(i), globalMap.getValue(i));
		}
		if(otherMap != null)
		{
			for(int i = 0;i < otherMap.size();i++ )
			{
				textsMap.put(otherMap.getKey(i), otherMap.getValue(i));
			}
		}
		m_nGlobalTextCount = globalMap.size();
	}

	/**
	 * Writes the fonts to the given stream. <br/>
	 * 
	 * @param out
	 * @throws Exception
	 */
	protected void writeFonts(OutputStream out) throws Exception
	{
		DataOutputStream dos = new DataOutputStream(out);

		int count = m_bitmapFonts.size();
		dos.writeByte(count);

		Iterator it = m_bitmapFonts.entrySet().iterator();
		while (it.hasNext())
		{

			m_nNumberOfFonts++;

			Map.Entry entry = (Map.Entry)it.next();
			String id = (String)entry.getKey();
			//System.out.println("id : "+id);
			if(id.equals(m_SoftKeysFontId))
			{
				m_nSoftKeysFont = m_FontIds.size();    //protected List m_FontIds = new ArrayList();
			}
			m_FontIds.add(id);
			String[] data = (String[])entry.getValue();

			//image id
			dos.writeShort(imageDescriptors.indexOf(data[0]));

			//the data file
			parseFontData(data[1]); 
			m_nNumFontsParsed++;

			//font height
			dos.writeByte(Integer.parseInt((String)m_fontAttributes.get(FONT_ATTRIBUTES_HEIGHT)));

			//font supports lower case?
			dos.writeByte(Integer.parseInt((String)m_fontAttributes.get(FONT_ATTRIBUTES_UPPER_CASE)));

			//font dat
			//String substr = m_fontData.substring(0,5);


			dos.writeUTF(m_fontData);   //static String m_fontData;

			dos.writeUTF(parseFontHashing((String)m_fontAttributes.get(FONT_ATTRIBUTES_HASHING)));
		}

		//the system fonts
		count = m_systemFonts.size();
		dos.writeByte(count);
		it = m_systemFonts.entrySet().iterator();
		while (it.hasNext())
		{
			Map.Entry entry = (Map.Entry)it.next();
			String id = (String)entry.getKey();
			if(id.equals(m_SoftKeysFontId))
			{
				m_nSoftKeysFont = m_FontIds.size();
			}
			m_FontIds.add(id);

			int[] data = (int[])entry.getValue();
			dos.writeByte(data[0]);
			dos.writeByte(data[1]);
			dos.writeByte(data[2]);
			dos.writeInt(data[3]);
		}


		// BDOYLE 29 May 2007 All the font data has been parsed so go ahead and create the fontdata.cpp and fontdata.h 
		if("BREW".equalsIgnoreCase(m_nTarget))
		{
			creatFontClassFiles();
		}


	}

	/**
	 * BDOYLE 29 May 2007 Creates the fontdata.cpp and fontdata.h . <br/>
	 * Writes all data into a stringbuffer creating each line of the file as they should appear in the .cpp and .h
	 * All the font specific data is gotten from arrays where it was parsed into in parseFontData(String file)
	 * 
	 * 
	 * @param out
	 * @throws Exception
	 */

	
	void creatFontClassFiles()
	{
		int nArg = m_nNumberOfFonts;
		//Headers for .cpp
		s1.append( "#include \"Fonts.h\" \n" );
		s1.append( "#include \"FontData.h\" \n \n" );


		s1.append("int numCharsPerFont[" + (nArg-1) + "] = {" +fontData[0][2] );

		for (int i=1; i<nArg-1; i++)
		{
			s1.append( "," +fontData[i][2] );
		}
		s1.append( "};\n" );

		s1.append("int heightOfFont[" +(nArg-1) + "] = {" + fontData[0][0] );
		for (int i=1; i<nArg-1; i++)
		{
			s1.append( "," +fontData[i][0]) ;
		}
		s1.append( "};\n" );


		s1.append("\n void FontData::Init()\n {}\n void FontData::destroy()\n {}\n FontData::~FontData()\n {}\n");


		//Create int FontData::getFontHeight( int  fontId)
		s1.append("int FontData::getFontHeight( int  fontId)\n{\n return heightOfFont[fontId]; \n} \n");

		for (int i = 1; i<nArg; i++) // number of files
		{
			s1.append("int FontData::fontData"+i+"["+fontData[i-1][2]+"][3] ={\n");
			if (fontData[i-1][2] > 0)
			{
				s1.append("{" + hashTableImageData[i-1][0][0]+","+hashTableImageData[i-1][0][1]+","+hashTableImageData[i-1][0][2]+"}");
			}
			for (int x = 1; x < fontData[i-1][2]; x++) // number of characters in that font set that the file contains
			{
				s1.append(",{" + hashTableImageData[i-1][x][0]+","+hashTableImageData[i-1][x][1]+","+hashTableImageData[i-1][x][2]+"}");
			}
			s1.append("};\n");
		}

		//Create int FontData::findArrayIndexForBitmapFont(int asciiCode, int fontId)
		s1.append( "\n int FontData::findArrayIndexForBitmapFont(int asciiCode, int fontId)\n{\n fontId+=1; \n");

		for (int i = 0; i <nArg; i++)
		{
			if ( i == 0)
			{
				s1.append("if (fontId =="+(i+1) +")\n{");
			}
			else
			{
				s1.append("else if (fontId =="+(i+1)+")\n{");
			}
		
			//std::cout <<"\n Seq count : "<<sequenceCount[i] ;
			for (int x=0; x <= sequenceCount[i]; x++)
			{
				if (x==0)
				{
					s1.append("if ( (asciiCode >="+hashTableOffset[i][x][0]+") && (asciiCode<="+hashTableOffset[i][x][1]+") )\n{\nreturn "+hashTableOffset[i][x][2]+";\n}");
				}
				else
				{
					s1.append("else if ( (asciiCode >="+hashTableOffset[i][x][0]+") && (asciiCode<="+hashTableOffset[i][x][1]+") )\n{\nreturn "+hashTableOffset[i][x][2]+";\n}");
				}
			}
			s1.append("\n}");
		}

		s1.append("return -1; \n}\n");
		
		//Create int FontData::getXFontImageOffset(int arrayLookupPosition,int fontId)
		s1.append( "\nint FontData::getXFontImageOffset(int arrayLookupPosition,int fontId)\n{\n fontId+=1; \n if (fontId == 1)\n{\nreturn fontData1[arrayLookupPosition][0];\n}\n");	

		for (int i = 2; i < nArg; i++)
		{
			s1.append( "else if (fontId == "+i +")\n{\nreturn fontData"+i+"[arrayLookupPosition][0];\n}\n");
		}
		s1.append( " return -1; \n}\n");

		s1.append( "int FontData::getYFontImageOffset(int arrayLookupPosition, int fontId)\n{\n fontId+=1; \nif (fontId == 1)\n{\nreturn fontData1[arrayLookupPosition][1];\n}\n");
		for (int i = 2; i < nArg; i++)
		{
			s1.append( "else if (fontId =="+i+")\n{\nreturn fontData"+i+"[arrayLookupPosition][1];\n}\n");
		}
		s1.append("return -1; \n }\n");

		s1.append("int FontData::charWidth( char  ch, int  fontId)\n{\n fontId+=1; \n unsigned char asciiCode = (unsigned char)ch;\n 	if (((asciiCode >= 97) && (asciiCode <= 122)) || ((asciiCode >= 224) && (asciiCode <= 254))) \n {\n asciiCode -= 32;\n ch = asciiCode;\n}\n int offset = findArrayIndexForBitmapFont(asciiCode, fontId-1);\nint arrayLookupPosition = asciiCode - offset;\n \n \n	if (asciiCode == 32)/n{/n return 6;/n }/n else/n{\n if (fontId == 1)\n{\nreturn fontData1[arrayLookupPosition][2];  // Returns the width of the font.\n}\n");
		for (int i = 2; i < nArg; i++)
		{
			s1.append("else if (fontId == "+i+")\n{\n\nreturn fontData"+i+"[arrayLookupPosition][2];  // Returns the width of the font.\n}");
		}
		s1.append("\n}\n return -1; \n}");
	
		// copying the stringstream to a string so as to writWee out to the file.
		//std::string s2 = s1.str();

		// Write the files 



		

		try
		{
			String filedata = s1.toString();

			String filename = "..\\BREW\\fontdata.cpp";
			BufferedWriter out = new BufferedWriter(new FileWriter(filename));
			out.write(filedata); 
			out.flush();
			out.close();
		}
		catch (Exception e)
		{
			System.out.println("Cannot create fontdata.cpp please check that you've checked it out in Alien Brain or that write permissions are set ");
			e.printStackTrace();
		}


		// Create the stringbuffer of the data for fontdata.h
		s3.append("#ifndef __FONTDATA_H__\n#define __FONTDATA_H__\n#include \"BrewString.h\"\n #include \"String.h\"\n class FontData \n{\nprivate:\npublic:\nstatic int getFontHeight( int  fontId);\nstatic int findArrayIndexForBitmapFont(int asciiCode, int fontId);\n static int charWidth( char  ch, int  fontId);\n static int getXFontImageOffset(int arrayLookupPosition, int fontId);\n static int getYFontImageOffset(int arrayLookupPosition, int fontId);\n static void Init( );\n static void destroy();\n ~FontData(); \n ");

		for (int i=1; i < nArg; i++)
		{
			s3.append("static int fontData" +i +"[" +fontData[i-1][2] +"][3];\n");
		}

		s3.append("};\n#endif");

		try
		{
			String filedata = s3.toString();

			String filename = "..\\BREW\\fontdata.h";
			BufferedWriter out = new BufferedWriter(new FileWriter(filename));
			out.write(filedata); 
			out.flush();
			out.close();
		}
		catch (Exception e)
		{
			System.out.println("Cannot create fontdata.h please check that you've checked it out in Alien Brain or that write permissions are set ");
			e.printStackTrace();
		}
	}


	void parseFontData(String file) throws Exception
	{
// BDoyle 29th May handle the parsing of the font data into arrays so that it can be parsed out into strings and used as in BREW as .cpp and .h files
		if("BREW".equalsIgnoreCase(m_nTarget))
		{
			m_fontAttributes.clear();
			m_fontData = null;
			FileInputStream fis = new FileInputStream(file);
			BufferedReader in = new BufferedReader(new InputStreamReader(fis));
			String line;
			int index;
			int offsetIntoSequance = 0;
			StringBuffer fontDataBuf = new StringBuffer();
			int num_lines = 0;
			while ((line = in.readLine()) != null)
			{
				if(line.startsWith(FONT_ATTRIBUTES_PREFIX))   //static final String FONT_ATTRIBUTES_PREFIX = "##";
				{
					//parse attribute
					line = line.substring(2, line.length()).trim();
					index = line.indexOf('=');
					String newbstr = line.substring(0, index);
					String newbstr2 = line.substring(index + 1);

					if("FONT_HEIGHT".equalsIgnoreCase(newbstr))
					{
						fontData[m_nNumFontsParsed][0] = Integer.parseInt(newbstr2);
					}
					else if("UPPER_CASE_ONLY".equalsIgnoreCase(newbstr))
					{
						fontData[m_nNumFontsParsed][3] = Integer.parseInt(newbstr2);
					}
					else if("CHAR_COUNT".equalsIgnoreCase(newbstr))
					{
						fontData[m_nNumFontsParsed][2] = Integer.parseInt(newbstr2);
					}
					else if("FONT_HASH".equalsIgnoreCase(newbstr))
					{
					
						String[] fontSequenceData = newbstr2.split(",");
				
						int numberOfSequences = fontSequenceData.length;
						sequenceCount[m_nNumFontsParsed] = numberOfSequences;


						int offset=0;
						
						for (int i=0; i < numberOfSequences; i++)
						{

							String[] tempData = fontSequenceData[i].split("-");
							hashTableOffset[m_nNumFontsParsed][i][0] = Integer.parseInt(tempData[0]);	
							hashTableOffset[m_nNumFontsParsed][i][1] = Integer.parseInt (tempData[1]);
		
							if (offset == 0)
							{
								offset = hashTableOffset[m_nNumFontsParsed][i][0];
							}
							else
							{
								offset+= (hashTableOffset[m_nNumFontsParsed][i][0] - (hashTableOffset[m_nNumFontsParsed][i-1][1] + 1));
							}
							hashTableOffset[m_nNumFontsParsed][i][2] = offset;
						}
					}
					m_fontAttributes.put(line.substring(0, index), line.substring(index + 1));
				}
				else if(line.length() > 0)
				{
					String[] charData = line.split(",");
					if(line.startsWith(","))//the character ','
					{
						for(int i = 1;i < charData.length;i++ )
						{
							charData[i - 1] = charData[i];
						}
						charData[0] = ",";
					}
					fontDataBuf.append((char)Integer.parseInt(charData[1])).append((char)Integer.parseInt(charData[2])).append((char)Integer.parseInt(charData[3]));

					hashTableImageData[m_nNumFontsParsed][num_lines][0] = Integer.parseInt(charData[1]);
					hashTableImageData[m_nNumFontsParsed][num_lines][1] = Integer.parseInt(charData[2]);
					hashTableImageData[m_nNumFontsParsed][num_lines][2] = Integer.parseInt(charData[3]);
					num_lines++;

				}
			}
			m_fontData = fontDataBuf.toString();
	
		}
		else // else it is Java and not BREW
		{
			
			m_fontAttributes.clear();
			m_fontData = null;
			FileInputStream fis = new FileInputStream(file);
			BufferedReader in = new BufferedReader(new InputStreamReader(fis));
			String line;
			int index;
			StringBuffer fontDataBuf = new StringBuffer();

			while ((line = in.readLine()) != null)
			{
				if(line.startsWith(FONT_ATTRIBUTES_PREFIX))   //static final String FONT_ATTRIBUTES_PREFIX = "##";
				{
					//parse attribute
					line = line.substring(2, line.length()).trim();
					index = line.indexOf('=');
					String newbstr = line.substring(0, index);
					String newbstr2 = line.substring(index + 1);
					System.out.println(" Att : "+newbstr +" : "+newbstr2);

					m_fontAttributes.put(line.substring(0, index), line.substring(index + 1));
				}
				else if(line.length() > 0)
				{
					String[] charData = line.split(",");
					if(line.startsWith(","))//the character ','
					{
						for(int i = 1;i < charData.length;i++ )
						{
							charData[i - 1] = charData[i];
						}
						charData[0] = ",";
					}
					fontDataBuf.append((char)Integer.parseInt(charData[1])).append((char)Integer.parseInt(charData[2])).append((char)Integer.parseInt(charData[3]));
				}
			}
			m_fontData = fontDataBuf.toString();
		}
	}

	String parseFontHashing(String hashing)
	{
		StringBuffer result = new StringBuffer();
		String[] pairs = hashing.split(",");
		int index;
		for(int i = 0;i < pairs.length;i++ )
		{
			index = pairs[i].indexOf('-');
			if(index > 0)
			{
				result.append((char)Integer.parseInt(pairs[i].substring(0, index))).append((char)Integer.parseInt(pairs[i].substring(index + 1)));
			}
		}
		return result.toString();
	}

	/**
	 * Writes the parsed palettes to the given output stream.
	 * 
	 * @param out
	 * @return filesize number of byte written to the file
	 * @throws Exception
	 */
	protected int writePalettes(OutputStream out) throws IOException
	{
		int filesize = 0;
		final int count = palettesList.size();
		if(count > 0)
		{
			int colorsCount;
			int value;
			out.write(count);
			filesize++ ;//one byte for the palette count
			for(int i = 0;i < count;i++ )
			{

				Map palette = (Map)palettesList.get(i);//color to color palette				
				colorsCount = palette.size() - 1;
				out.write(colorsCount);
				filesize++ ;//one byte for the color count of each palette
				Iterator it = palette.entrySet().iterator();
				while (it.hasNext())
				{

					Map.Entry replacement = (Map.Entry)it.next();
					//palette index
					if(replacement.getKey() instanceof Integer)
					{
						value = ((Integer)replacement.getKey()).intValue();
						IOUtils.writeColor(value, out);
						//replacement color
						value = ((Integer)replacement.getValue()).intValue();
						IOUtils.writeColor(value, out);
					}
					filesize += 6;//four byte for each color in the palette
				}
			}

		}
		return filesize;
	}

	/**
	 * lookupPalette
	 * @param strPalette - string containing palette name
	 * 
	 * Returns palette index (or -1 if none)
	 * Converts palette from-colors to indexes (if not done already for this particular palette)
	 */
	protected int lookupPalette(String strPath, String strPalette, String utilDir) throws IOException
	{
		int paletteID = -1;
		if(strPalette != null && strPalette.length() > 0)
		{
			if((Integer)paletteIdMap.get(strPalette) != null)
			{
				paletteID = ((Integer)paletteIdMap.get(strPalette)).intValue();

				//				if(palettesIndexList.get(paletteID) == null)
				//				{
				//					// get colors from png; convert to indexes
				//					String strReadLine;
				//
				//					BufferedReader reader = executeApp(utilDir, "getpalette.exe", new String[]
				//					{"-d", strPath});
				//
				//					ArrayList colors = new ArrayList();
				//					while ((strReadLine = reader.readLine()) != null)
				//					{
				//						colors.add(new Integer(Integer.parseInt(strReadLine)));
				//					}
				//
				//					reader.close();
				//
				//					// Map RGB colors to indexes
				//					Map RGBPalette = (Map)palettesList.get(paletteID);
				//					Map IndexPalette = new LinkedHashMap();
				//					IndexPalette.put("id", RGBPalette.get("id"));
				//					for(int i = 0;i < colors.size();i++ )
				//					{
				//						Integer toCol = (Integer)RGBPalette.get((Integer)colors.get(i));
				//						if(toCol != null)
				//						{
				//							IndexPalette.put(new Integer(i), toCol);
				//						}
				//					}
				//
				//					// Store index mappings instead of RGB colors
				//					palettesIndexList.set(paletteID, IndexPalette);
				//				}
			}
			else
			{
				System.out.println("No Palette ID " + strPalette + " found.");
			}
		}
		return paletteID;
	}
	
	/*
	for(int i = 0;i < soundMap.size();i++ )
	{
		SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
		if(sound.getAudioFormat() != null && sound.getAudioFormat().compareToIgnoreCase("midi") == 0 && sound.getPath() != null && !midiMap.containsKey(sound.getPath()))
		{
			midiMap.put(sound.getPath(), sound);//##
			System.out.println("############################################");
			System.out.println(sound.getPath());
			System.out.println("############################################");
		}
	}*/
	
	//writes the raw midi data into a .brc file for use in brew
	protected void writeBrewSounds() throws Exception//##
	{
		//@ian adapted this code from writeImages
		byte[] copyBuffer = new byte[4096];
		int bytesRead = 0;
		
		for(int i = 0;i < soundMap.size();i++ )
		{
			//ImageDescriptor image = (ImageDescriptor)imageDescriptors.getValue(i);
			SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
			System.out.println("sound " + sound);
			System.out.println("sound.getPath() " + sound.getPath());
			
			String fileId = "SOUND_ID";//+ sound.getName().replace('.', '_').toUpperCase() ;
			//fileId.concat(GetFormalName(soundMap.getKey(i)));
			
			fileId = fileId + "_" + GetFormalName(soundMap.getKey(i).toUpperCase());
	
			//CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND).append(GetFormalName(soundMap.getKey(i))).append('=').append(Integer.toString(i)).append(";\n");
			System.out.println("Sound: " + i + " GetFormalName " + GetFormalName(soundMap.getKey(i)));
			System.out.println("FileID " + fileId);
			//if( !image.isTransform())
			{
				
				String rawPath = sound.getPath();//todo remove this doesnt seem to be used
				try
				{
					if(brewResStream==null)
					{
						System.out.println("brewResStream==null");
					}
					
					System.out.println("File.separatorChar: " + File.separatorChar);
					System.out.println("rawPath " + rawPath);
					
					String srcPath = rawPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
					//File outFile = new File("brew-resources/"+ fileId + ".bmp");
					//String outPath = outFile.getAbsolutePath();
					//executeApp(utilDir, "png2bmp", new String[]
					//{"-o", outPath, srcPath}, true).close();
					
					if(brewResStream==null)
					{
						System.out.println("brewResStream==null");
					}
					brewResStream.write(fileId.getBytes());
					brewResStream.write('\t');
					brewResStream.write(String.valueOf(i).getBytes());
					brewResStream.write('\t');
					//@ian note we just mark as image as this file isnt desifned for sounds.
					brewResStream.write("IMAGE \"audio/mid\"\t=".getBytes());
					FileInputStream fis = new FileInputStream(srcPath);
					ByteArrayOutputStream baos = new ByteArrayOutputStream(fis.available());
					while ((bytesRead = fis.read(copyBuffer)) != -1)
					{
						baos.write(copyBuffer, 0, bytesRead);
					}
					fis.close();
					baos.flush();
					byte data[] = baos.toByteArray();
					//##
					for(int j = 0;j < data.length;j++ )
					{
						String toHex = Integer.toHexString(data[j]);
						if(toHex.length() == 1)
						{
							toHex = "0"+toHex;
						}
						else if(toHex.length() > 2)
						{
							toHex = toHex.substring(toHex.length()-2);
						}
							
						brewResStream.write(toHex.getBytes());
					}
					brewResStream.write('\n');
					baos.close();
				}
				catch (Exception e)
				{
					e.printStackTrace();
				}
			}
			/*
			else
			{
				brewResStream.write(fileId.getBytes());
				brewResStream.write('\t');
				brewResStream.write(String.valueOf(i).getBytes());
				brewResStream.write('\t');
				brewResStream.write("IMAGE \"image/bmp\"\t=00".getBytes());
				brewResStream.write('\n');
			}
			*/
		}
		brewResStream.flush();
		//brewResStream.close();
	}
		

	/**
	 * Writes all the images, both raw and transformed and saves the indices.
	 * 
	 * @param type
	 * @param out
	 * @throws Exception
	 */
	protected void writeImages(OutputStream out, String resourcesDir, String utilDir, String device, String lang, boolean usePNGDecompressor, boolean forceDecompression, String target) throws IOException
	{
		String path = (String)imageTypes.get("png");
		
		if(path == null)
		{
			throw new IllegalArgumentException("Images type 'png' was not configured!");
		}
		else
		{
			// Get list of images and their paths
			Properties imagePaths = new Properties();
			InputStream in = new FileInputStream(path);
			imagePaths.load(in);
			in.close();

			// Override with device-specific images
			path = (String)imageTypes.get("device");
			int nDevicePathPos = path.indexOf("${device}");
			if(nDevicePathPos >= 0)
			{
				path = path.substring(0, nDevicePathPos) + device + path.substring(nDevicePathPos + 9);
			}
			if(new File(path).exists())
			{
				Properties rawImagesDevice = new Properties();
				in = new FileInputStream(path);
				rawImagesDevice.load(in);
				in.close();
				Iterator it = rawImagesDevice.keySet().iterator();
				while (it.hasNext())
				{
					String key = (String)it.next();
					if(((String)rawImagesDevice.get(key)).length() == 0)
					{
						imagePaths.remove(key);
					}
					else
					{
						imagePaths.put(key, rawImagesDevice.get(key));
					}
				}
			}

			writeGameImages(imagePaths);

			if(imagePaths.size() > 0)
			{
				// Seperate icon from rest of images
				if(imagePaths.get("icon") != null)
				{
					String strIcon = (String)imagePaths.get("icon");
					imagePaths.remove("icon");
					copyFile(strIcon, resourcesDir + "i.png");
				}
				
				if(imagePaths.get("c-icon") != null)
				{
					String strIcon = (String)imagePaths.get("c-icon");
					imagePaths.remove("c-icon");
					copyFile(strIcon, resourcesDir + "c-i.png");
				}
				if(imagePaths.get("s-icon") != null)
				{
					String strIcon = (String)imagePaths.get("s-icon");
					imagePaths.remove("s-icon");
					copyFile(strIcon, resourcesDir + "s-i.png");
				}
				if(imagePaths.get("z-icon") != null)
				{
					String strIcon = (String)imagePaths.get("z-icon");
					imagePaths.remove("z-icon");
					copyFile(strIcon, resourcesDir + "z-i.png");
				}
				
				if(imagePaths.get("landscape_warning") != null)
				{
					String strW = (String)imagePaths.get("landscape_warning");
					imagePaths.remove("landscape_warning");
					copyFile(strW, resourcesDir + "w.png");
				}
				
				if(imagePaths.get("popcap-logo") != null)
				{
					String strLogo = (String)imagePaths.get("popcap-logo");
					imagePaths.remove("popcap-logo");
					copyFile(strLogo, resourcesDir + "l.png");
				}

				for(int i = 1;i < 10;i++ )
				{
					String id = "popcap-logo-" + i;
					if(imagePaths.get(id) != null)
					{
						String strLogo = (String)imagePaths.get(id);
						imagePaths.remove(id);
						copyFile(strLogo, resourcesDir + "l" + i + ".png");
					}
				}

				// Create image descriptors for each image and transformation
				Set imagePathKeySet = imagePaths.keySet();
				Iterator it = imagePathKeySet.iterator();
				while (it.hasNext())
				{
					// Add image
					String key = (String)it.next();
					ArrayList sets = null;
					if(m_images.get(key) != null)
					{
						//Every Image has a "set" key that contains the sets in an ArrayList
						sets = (ArrayList)((LinkedHashMap)m_images.get(key)).get("set");
					}

					// Modify path to include language in the path
					String imagePath = (String)imagePaths.get(key);
					int nLangPathPos = imagePath.indexOf("${lang}");
					if(nLangPathPos >= 0)
					{
						imagePath = imagePath.substring(0, nLangPathPos) + lang + imagePath.substring(nLangPathPos + 7);
					}

					ImageDescriptor img = new ImageDescriptor(key, imagePath, sets);
					imageDescriptors.put(img.getName(), img);

					// PNG must exist
					String strPNGName = img.getPath();
					strPNGName = strPNGName.substring(0, strPNGName.lastIndexOf(".")) + ".png";
					File filePNG = new File(strPNGName);
					if( !filePNG.exists())
					{
						throw new IOException("*** PNG not found: " + strPNGName);
					}

					// Create JMP if necessary
					if(img.getExtension().compareToIgnoreCase("jmp") == 0)
					{
						File fileJMP = new File(img.getPath());

						// Create JMP if it's a different version
						if( !fileJMP.exists() || fileJMP.lastModified() != filePNG.lastModified())
						{
							executeApp(utilDir, "imagetojmp.exe", new String[]
							{filePNG.getPath()}, false).close();
							fileJMP.setLastModified(filePNG.lastModified());
						}
					}

					// Add image's transformations 
					ArrayList transforms = (ArrayList)m_transformedImages.get(key);
					if(transforms != null)
					{
						for(int nTransform = 0;nTransform < transforms.size();nTransform++ )
						{
							LinkedHashMap trans = (LinkedHashMap)transforms.get(nTransform);
							boolean bHFlip = trans.containsKey(IMAGE_TRANSFORM_NAME_FLIP_HORIZONTAL);
							boolean bVFlip = trans.containsKey(IMAGE_TRANSFORM_NAME_FLIP_VERTICAL);
							int nRotateValue = 0;
							if(trans.containsKey(IMAGE_TRANSFORM_NAME_ROTATE))
							{
								nRotateValue = Integer.parseInt((String)trans.get(IMAGE_TRANSFORM_NAME_ROTATE));
							}
							nRotateValue /= 90;
							nRotateValue %= 4;
							if(nRotateValue >= 2)
							{
								nRotateValue -= 2;
								bHFlip = !bHFlip;
								bVFlip = !bVFlip;
							}
							ImageDescriptor imgTrans = new ImageDescriptor(img, (String)trans.get("id"), (ArrayList)trans.get("set"), bHFlip, bVFlip, nRotateValue != 0, lookupPalette(img.getPath(), (String)trans.get(IMAGE_TRANSFORM_NAME_PALETTE_SHIFT), utilDir));
							imageDescriptors.put(imgTrans.getName(), imgTrans);
						}
					}
				}
				m_nImageCount = imageDescriptors.size();

				// Apply image flags
				int[] flags = calculateImageFlags();
				for(int i = 0;i < imageDescriptors.size();i++ )
				{
					((ImageDescriptor)imageDescriptors.getValue(i)).addSets(flags[i]);
				}

				// Write Images
				
				byte[] copyBuffer = new byte[4096];
				int bytesRead = 0;
				if("BREW".equalsIgnoreCase(target))
				{					
					System.out.println("RESOURCE HANDLER MESSAGE: Target platform is BREW, generating BMP...");
					//CreateBrewResFile();
					/*
					File outputFolder = new File("brew-resources");
					if( !outputFolder.exists())
					{
						outputFolder.mkdir();
						System.out.println("RESOURCE HANDLER MESSAGE: Created folder " + outputFolder.getAbsolutePath());
					}
					//##
					File brcFile = new File(outputFolder, "resources.brc");
					DataOutputStream resourcesOut = new DataOutputStream(new FileOutputStream(brcFile));
					
					NOTE@ ian, I'm changing from using a local DataOutputStream resourcesOut to a glaobal FileOutputStream brewResStream
					*/
					
					for(int i = 0;i < imageDescriptors.size();i++ )
					{
						ImageDescriptor image = (ImageDescriptor)imageDescriptors.getValue(i);
						String fileId = IMAGE_ID + image.getName().replace('.', '_').toUpperCase() ;
						if( !image.isTransform())
						{
							String rawPath = image.getPath();
							try
							{
								String srcPath = rawPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
								File outFile = new File("brew-resources/"+ fileId + ".bmp");
								String outPath = outFile.getAbsolutePath();
								executeApp(utilDir, "png2bmp", new String[]
								{"-q -o", outPath, srcPath}, true).close();//@ian -q puts in quiet mode..no printouts

								brewResStream.write(fileId.getBytes());
								brewResStream.write('\t');
								brewResStream.write(String.valueOf(i).getBytes());
								brewResStream.write('\t');
								brewResStream.write("IMAGE \"image/bmp\"\t=".getBytes());
								FileInputStream fis = new FileInputStream(outFile);
								ByteArrayOutputStream baos = new ByteArrayOutputStream(fis.available());
								while ((bytesRead = fis.read(copyBuffer)) != -1)
								{
									baos.write(copyBuffer, 0, bytesRead);
								}
								fis.close();
								baos.flush();
								byte data[] = baos.toByteArray();
								for(int j = 0;j < data.length;j++ )
								{
									String toHex = Integer.toHexString(data[j]);
									if(toHex.length() == 1)
									{
										toHex = "0"+toHex;
									}
									else if(toHex.length() > 2)
									{
										toHex = toHex.substring(toHex.length()-2);
									}
										
									brewResStream.write(toHex.getBytes());
								}
								brewResStream.write('\n');
								baos.close();
							}
							catch (Exception e)
							{
								e.printStackTrace();
							}
						}
						else//@ian image is a transform of another image, so we dont need its data
						{
							brewResStream.write(fileId.getBytes());
							brewResStream.write('\t');
							brewResStream.write(String.valueOf(i).getBytes());
							brewResStream.write('\t');
							brewResStream.write("IMAGE \"image/bmp\"\t=00".getBytes());
							brewResStream.write('\n');
						}
					}
					brewResStream.flush();
					brewResStream.close();
				}
				else
				{
					ByteArrayOutputStream baos = new ByteArrayOutputStream(256000);
					DataOutputStream dos = new DataOutputStream(baos);
					File tempFolder = new File("decompressed-pngs");
					if(forceDecompression || usePNGDecompressor)
					{
						System.out.println("RESOURCE HANDLER MESSAGE: using png decompresser...");
						if( !tempFolder.exists())
						{
							tempFolder.mkdir();
							System.out.println("RESOURCE HANDLER MESSAGE: Created folder " + tempFolder.getAbsolutePath());
						}
					}
					else
					{
						System.out.print("NOT using png decompresser...");

					}
					int originalL = 0;
					for(int i = 0;i < imageDescriptors.size();i++ )
					{
						ImageDescriptor image = (ImageDescriptor)imageDescriptors.getValue(i);
						String rawPath = image.getPath();

						// Read image data
						byte[] buffer = null;
						if( !image.isTransform())
						{
							// If decompressing PNGs and this image has flips
							//if a png decompressor was defined and if the image has flipped children,
							//it will be decompressed to a temporary file and the resulting image will be used for the resources file 
							if(forceDecompression || (usePNGDecompressor && image.hasFlippedChildren()))
							{
								try
								{
									String srcPath = rawPath.replace('/', File.separatorChar).replace('\\', File.separatorChar);
									File srcFile = new File(srcPath);

									File outFile = new File(tempFolder, srcFile.getName());
									String outPath = outFile.getAbsolutePath();
									FileInputStream fin = new FileInputStream(srcFile);
									FileOutputStream fos = new FileOutputStream(outFile);
									while ((bytesRead = fin.read(copyBuffer)) != -1)
									{
										fos.write(copyBuffer, 0, bytesRead);
									}
									fin.close();
									fos.flush();
									fos.close();
									executeApp(utilDir, "UncompressPng", new String[]
									{"-k", outPath}, false).close();
									rawPath = outPath;
								}
								catch (Exception e)
								{
									e.printStackTrace();
								}
							}

							File file = new File(rawPath.trim());
							if(file.exists())
							{
								int nFileLength = (int)file.length();
								originalL += nFileLength;
								if(nFileLength > 0)
								{
									if(nFileLength > m_nMaxImageSize)
									{
										m_nMaxImageSize = nFileLength;
									}
									FileInputStream imageIn = new FileInputStream(file);
									buffer = new byte[nFileLength];
									imageIn.read(buffer);
									imageIn.close();
								}
							}
						}

						// Write image data
						int nSetFlags = image.getSets();
						if(nSetFlags == 1)
						{
							System.out.println("image " + image.getName() + " is of set 0 and will be in memory all the time!");
						}
						dos.writeInt(nSetFlags);
						if(buffer == null)
						{
							dos.writeShort(0);
						}
						else
						{
							dos.writeShort(buffer.length);
							dos.writeInt(image.getChildSets());
							dos.write(buffer);
						}
					}
					byte[] data = baos.toByteArray();

					System.out.println("Original l: " + originalL + " Resulting image length:" + data.length);
					out.write(data);
				}
			}
		}
	}

	protected void writeGameImages(Properties images)
	{

	}

	/**
	 * Calculates the flags for each sound.
	 * @param flags
	 */
	protected void calculateSoundFlags(int[] flags)
	{

	}

	/**
	 * Calculates the flags for each image.
	 * @param flags
	 */
	protected int[] calculateImageFlags()
	{
		int[] flags = new int[imageDescriptors.size()];
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			flags[i] = 0;
		}
		return flags;
	}

	/**
	 * Writes the game specific data file. 
	 * @param out
	 * @throws Exception
	 */
	public void writeGameData(OutputStream out) throws Exception
	{
	}

	protected void writeSounds(OutputStream out, String device, String utilDir, boolean bSoundsSeparate, String strSoundResourcePrefix, String resourcesDir) throws IOException
	{
		
		// Flags for determining usage of defaults
		boolean[] soundFormatSpecified = new boolean[soundMap.size()];
		boolean[] soundPathSpecified = new boolean[soundMap.size()];
		

		// Get preferred formats and paths for this device
		for(int attrib = 0;attrib < soundAttributes.size();attrib++ )
		{
			SoundTypeDescriptor desc = (SoundTypeDescriptor)soundAttributes.get(attrib);

			Properties properties = new Properties();
			InputStream in = new FileInputStream(desc.getPath());
			properties.load(in);
			in.close();

			if(desc.getDevice() != null && (desc.getDevice().toLowerCase().compareTo(device) == 0))
			{
				if(desc.getFormat() == null)
				{
					// Get format preference
					for(int i = 0;i < soundMap.size();i++ )
					{
						SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
						String property = properties.getProperty(soundMap.getKey(i));//##
						if(property != null)
						{
							// add sound if it exists in the properties
							if(sound.getAudioFormat() == null || (sound.getAudioFormat().compareTo(property) == 0))
							{
								sound.setAudioFormat(property);
								soundFormatSpecified[i] = true;
							}
							else
							{
								System.out.println("conflicting sound formats for '" + soundMap.getKey(i) + "'");
							}
						}
					}
				}
				else
				{
					// Get device-specific sound
					for(int i = 0;i < soundMap.size();i++ )
					{
						SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
						String property = properties.getProperty(soundMap.getKey(i));
						if(property != null)
						{
							if(sound.getAudioFormat() == null)
							{
								sound.setAudioFormat(desc.getFormat().toLowerCase());
							}
							if(sound.getPath() != null)
							{
								System.out.println("duplicate device-specific sound for '" + soundMap.getKey(i) + "'");
							}
							else if(sound.getAudioFormat() != desc.getFormat().toLowerCase())
							{
								System.out.println("conflicting sound formats for '" + soundMap.getKey(i) + "'");
							}
							else
							{
								if(property.length() > 0)
								{
									sound.setPath(property);
								}
								soundPathSpecified[i] = true;
							}
						}
					}
				}
			}
		}
		

		// Fill in remaining sounds w/ defaults
		for(int attrib = 0;attrib < soundAttributes.size();attrib++ )
		{
			SoundTypeDescriptor desc = (SoundTypeDescriptor)soundAttributes.get(attrib);

			Properties properties = new Properties();
			InputStream in = new FileInputStream(desc.getPath());
			properties.load(in);
			in.close();

			if(desc.getFormat() != null && desc.getDevice() == null)
			{
				// Check if this is a valid format for this device
				if(audioFormatMap.containsKey(desc.getFormat()))
				{
					// Get format-specific sound
					for(int i = 0;i < soundMap.size();i++ )
					{
						SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
						if( !soundPathSpecified[i])
						{
							String property = properties.getProperty(soundMap.getKey(i));
							if(property != null)
							{
								if( !soundFormatSpecified[i])
								{
									if(sound.getPath() == null)
									{
										sound.setAudioFormat(desc.getFormat().toLowerCase());
										sound.setPath(property);
									}
									else
									{
										System.out.println("unresolved multiple format-specific sounds for '" + soundMap.getKey(i) + "'");
									}
								}
								else if(sound.getAudioFormat().compareTo(desc.getFormat().toLowerCase()) == 0)
								{
									sound.setPath(property);
								}
							}
						}
					}
				}
			}
		}
		
		if("BREW".equalsIgnoreCase(m_nTarget))
		{
			try 
			{
				writeBrewSounds();
			}
			catch (Exception e)
			{
				e.printStackTrace();
			}
			//return;
		}

		// Create blank offset lists
		int[] MergedSoundStartOffset = new int[soundMap.size()];
		int[] MergedSoundEndOffset = new int[soundMap.size()];

		// Write midi strip, if used
		AudioFormatDescriptor midiFormat = (AudioFormatDescriptor)audioFormatMap.get("midi");
		if(midiFormat != null && midiFormat.isStrip())
		{
			ArrayMap midiMap = new ArrayMap();
			System.out.println("############################################");
			for(int i = 0;i < soundMap.size();i++ )
			{
				SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
				if(sound.getAudioFormat() != null && sound.getAudioFormat().compareToIgnoreCase("midi") == 0 && sound.getPath() != null && !midiMap.containsKey(sound.getPath()))
				{
					midiMap.put(sound.getPath(), sound);//##
					
					System.out.println(sound.getPath());
					
				}
			}
			System.out.println("############################################");

			if(midiMap.size() > 0)
			{
				// Merge midis & parse offsets
				String[] params = new String[midiMap.size() + 1];
				params[0] = MERGED_MIDI_NAME;
				for(int i = 0;i < midiMap.size();i++ )
				{
					params[i + 1] = midiMap.getKey(i);
				}
				BufferedReader reader = executeApp(utilDir, "midimerge.exe", params, false);
				int[] startOffsets = new int[midiMap.size()];
				int[] endOffsets = new int[midiMap.size()];
				for(int i = 0;i < midiMap.size();i++ )
				{
					String[] offsets = reader.readLine().split(" ");
					startOffsets[i] = Integer.parseInt(offsets[0]);
					endOffsets[i] = Integer.parseInt(offsets[1]);
				}
				for(int i = 0;i < soundMap.size();i++ )
				{
					SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
					int nIndex = midiMap.indexOf(sound.getPath());
					if(nIndex != -1)
					{
						if(sound.getStartPos() != -1)
						{
							// custom device-specific offsets
							MergedSoundStartOffset[i] = sound.getStartPos();
							MergedSoundEndOffset[i] = sound.getEndPos();
						}
						else
						{
							MergedSoundStartOffset[i] = startOffsets[nIndex];
							MergedSoundEndOffset[i] = endOffsets[nIndex];
						}
					}
				}
				reader.close();

				// Apply merged strip to sounds
				for(int i = 0;i < soundMap.size();i++ )
				{
					String strFormat = ((SoundDescriptor)soundMap.getValue(i)).getAudioFormat();
					if(strFormat != null && strFormat.compareToIgnoreCase("midi") == 0)
					{
						((SoundDescriptor)soundMap.getValue(i)).setPath(MERGED_MIDI_NAME);
					}
				}
			}
		}

		// Read sound data
		int nMaxSoundDataSize = 0;
		for(int i = 0;i < soundMap.size();i++ )
		{
			SoundDescriptor desc = (SoundDescriptor)soundMap.getValue(i);
			if(desc.getPath() != null)
			{
				if( !soundDataMap.containsKey(desc.getPath()))
				{
					//				System.out.println("Reading sound data for " + desc.getPath());
					InputStream in = new FileInputStream(desc.getPath());
					int read = 0;
					ByteArrayOutputStream bo = new ByteArrayOutputStream();
					while (true)
					{
						read = in.read();
						if(read == -1)
							break;
						else
							bo.write(read);
					}
					byte[] data = bo.toByteArray();
					soundDataMap.put(desc.getPath(), data);
					if( !bSoundsSeparate)
					{
						if(data.length > nMaxSoundDataSize)
						{
							nMaxSoundDataSize = data.length;
						}
					}
					in.close();
				}
				desc.setDataIndex(soundDataMap.indexOf(desc.getPath()));
			}
		}

		// clean up merged midi (if any)
		new File(MERGED_MIDI_NAME).delete();

		//Write Output File
		DataOutputStream dout = new DataOutputStream(out);
		dout.writeByte(soundDataMap.size()); // Number of sound data elements
		dout.writeShort(nMaxSoundDataSize); // Size of the largest sound data
		for(int i = 0;i < soundDataMap.size();i++ )
		{
			byte[] data = (byte[])soundDataMap.getValue(i);

			// Identify which sounds use this data
			int nItems = 0;
			int nSetFlags = 0;
			ArrayList usedSounds = new ArrayList();
			AudioFormatDescriptor audioFormatDescriptor = null;
			for(int soundID = 0;soundID < soundMap.size();soundID++ )
			{
				SoundDescriptor sd = (SoundDescriptor)soundMap.getValue(soundID);
				if(sd.getDataIndex() == i)
				{
					usedSounds.add(new Integer(soundID));

					// get audio format of any of the used sounds (all should be the same)
					audioFormatDescriptor = (AudioFormatDescriptor)audioFormatMap.get(sd.getAudioFormat());
				}
				nSetFlags |= sd.m_nSet; // combine set flags from all sounds that use this data
			}

			dout.writeInt(nSetFlags); // set flags
			dout.writeByte(usedSounds.size()); // number of sounds that use this data

			// write index type and data length
			if(audioFormatDescriptor.isStrip())
			{
				dout.writeByte(SOUND_INDEX_MIDI_STRIP);
				dout.writeByte(8);
			}
			else if(bSoundsSeparate)
			{
				dout.writeByte(SOUND_INDEX_RESOURCE);
				dout.writeByte(0);
			}
			else
			{
				dout.writeByte(SOUND_INDEX_NORMAL);
				dout.writeByte(0);
			}

			for(int item = 0;item < usedSounds.size();item++ )
			{
				// write index
				int nIndex = ((Integer)usedSounds.get(item)).intValue();
				dout.writeByte(nIndex);

				if(audioFormatDescriptor.isStrip())
				{
					// output index of each used sound in midi strip
					dout.writeInt(MergedSoundStartOffset[nIndex]); // start index for midi strips
					dout.writeInt(MergedSoundEndOffset[nIndex]); // end index for midi strips
				}
			}

			if(bSoundsSeparate)
			{
				String strExtension = soundDataMap.getKey(i);
				strExtension = strExtension.substring(strExtension.lastIndexOf("."));
				String strResourceName = "" + i + strExtension;
				dout.writeUTF(strSoundResourcePrefix + strResourceName);
				FileOutputStream outSound = new FileOutputStream(resourcesDir + strResourceName);
				outSound.write(data);
				outSound.close();
			}
			else
			{
				dout.writeShort(data.length); // sound size
				dout.write(data); // sound data
			}
		}
	}

	/**
	 * Writes all the ids of the different resources as a Java interface.
	 * 
	 * @param out
	 * @throws IOException
	 */
	public void writeConstantsInterface(String name, OutputStream out, Properties deviceProperties) throws Exception
	{
		if("BREW".equalsIgnoreCase(m_nTarget))
		{
			writeBrewConstants(name, out, deviceProperties);
			return;
		}
		CONSTANTS_INTERFACE.delete(0, CONSTANTS_INTERFACE.length());

//		CONSTANTS_INTERFACE.append(" TEST import javax.microedition.lcdui.Font;\n");
//		CONSTANTS_INTERFACE.append("\n");

		CONSTANTS_INTERFACE.append(CONSTANTS_INTERFACE_PREFIX.replaceAll("%NAME%", name));
		CONSTANTS_INTERFACE.append(CONSTANTS_INTERFACE_PALETTE_FILE_SIZE + paletteFileSize + ";\n");

		CONSTANTS_INTERFACE.append("//Text IDs\n");
		for(int i = 0;i < textsMap.size();i++ )
		{
			CONSTANTS_INTERFACE.append("    static final int TEXT_ID_").append(GetFormalName(textsMap.getKey(i))).append('=').append(Integer.toString(i)).append(";\n");
		}
		CONSTANTS_INTERFACE.append("    static final int MAX_TEXT_ID = " + textsMap.size() + ";\n");
		CONSTANTS_INTERFACE.append("    static final int NUM_COMMON_TEXTS = " + (textsMap.size() - m_nGlobalTextCount) + ";\n");
		CONSTANTS_INTERFACE.append("    static final int NUM_GLOBAL_TEXTS = " + m_nGlobalTextCount + ";\n");
		CONSTANTS_INTERFACE.append("    static final String TEXT_LANGUAGE_FILENAMES = \"\"");
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguage(nLanguage);
			CONSTANTS_INTERFACE.append(" +\n        (char)TEXT_ID_LANGUAGE_FILENAME_" + strLanguage.toUpperCase().replace('-', '_'));
		}
		CONSTANTS_INTERFACE.append(";\n");
		CONSTANTS_INTERFACE.append("    static final String TEXT_LANGUAGE_NAMES = \"\"");
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguageDescriptor(nLanguage);
			CONSTANTS_INTERFACE.append(" +\n        (char)TEXT_ID_" + strLanguage);
		}
		CONSTANTS_INTERFACE.append(";\n");
		String orientations = "\"\"";
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguage(nLanguage);
			CONSTANTS_INTERFACE.append("    static final int LANGUAGE_" + strLanguage.toUpperCase().replace('-', '_') + " = " + nLanguage + ";\n");
			orientations += "+(char)" +textConfiguration.getLanguageOrientation(nLanguage);
		}
		
		CONSTANTS_INTERFACE.append("    static final String LANGUAGE_ORIENTATIONS  = " + orientations + ";\n");
		CONSTANTS_INTERFACE.append("    static final int MAX_LANGUAGE = " + textConfiguration.getLanguageCount() + ";\n");

		CONSTANTS_INTERFACE.append("//Image IDs\n");
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append(ID_PREFIX_IMAGE).append(((ImageDescriptor)imageDescriptors.getValue(i)).getName().replace('.', '_').toUpperCase()).append(" = ").append(i).append(";\n");
		}
		CONSTANTS_INTERFACE.append(ID_PREFIX_IMAGE_MAX).append(m_nImageCount).append(";\n");
		CONSTANTS_INTERFACE.append(ID_PREFIX_IMAGE_SIZE_MAX).append(m_nMaxImageSize).append(";\n");

		writeConstantDefinitions(paletteIdMap, ID_PREFIX_PALETTE, "//Palette IDs");

		//the font ids
		CONSTANTS_INTERFACE.append("//Font Ids\n");
		for(int i = 0;i < m_FontIds.size();i++ )
		{
			CONSTANTS_INTERFACE.append(ID_PREFIX_FONT_ID).append(GetFormalName((String)m_FontIds.get(i))).append(" = ").append(i).append(";\n");
		}
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOFT_KEYS_FONT_ID).append(m_nSoftKeysFont).append(";\n");

		// write sound index types
		CONSTANTS_INTERFACE.append("//Sound Index Types\n");
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND_INDEX).append("NORMAL = " + SOUND_INDEX_NORMAL + ";\n");
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND_INDEX).append("MIDI_STRIP = " + SOUND_INDEX_MIDI_STRIP + ";\n");
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND_INDEX).append("RESOURCE = " + SOUND_INDEX_RESOURCE + ";\n");

		// write sound ids
		CONSTANTS_INTERFACE.append("//Sound IDs\n");
		for(int i = 0;i < soundMap.size();i++ )
		{
			SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
			CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND).append(GetFormalName(soundMap.getKey(i))).append('=').append(Integer.toString(i)).append(";\n");
		}
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOUND_MAX).append(Integer.toString(soundMap.size())).append(";\n");

		// write sound formats
		CONSTANTS_INTERFACE.append(CONSTANTS_SOUND_FORMAT);
		for(int i = 0;i < soundMap.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)");
			SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
			if(sound.m_strPath == null)
			{
				CONSTANTS_INTERFACE.append("-1");
			}
			else
			{
				CONSTANTS_INTERFACE.append(CONSTANT_PREFIX_AUDIO_FORMAT).append(GetFormalName(sound.m_strAudioFormat));
			}
		}
		CONSTANTS_INTERFACE.append(";\n");

		// write image formats
		CONSTANTS_INTERFACE.append(CONSTANTS_IMAGE_FORMAT);
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)").append(CONSTANT_PREFIX_IMAGE_FORMAT).append(((ImageDescriptor)imageDescriptors.getValue(i)).getExtension().toUpperCase());
		}
		CONSTANTS_INTERFACE.append(";\n");

		// write image transforms
		CONSTANTS_INTERFACE.append(CONSTANTS_IMAGE_TRANSFORM);
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)").append(((ImageDescriptor)imageDescriptors.getValue(i)).getTransformation());
		}
		CONSTANTS_INTERFACE.append(";\n");

		CONSTANTS_INTERFACE.append("//Explicit constants\n");
		writeExplicitConstants();

		writeGameDataConstants();
		CONSTANTS_INTERFACE.append("//ID Sequences\n");
		writeSequencesConstants();

		CONSTANTS_INTERFACE.append("//Layouts\n");
		writeLayoutsConstants();

		CONSTANTS_INTERFACE.append("//Vectors\n");
		writeVectorsConstants();
		
		CONSTANTS_INTERFACE.append("//Image Set Ids\n");
		writeSetIds();

		CONSTANTS_INTERFACE.append("//Game Boards - for mindbender.\n");
		CONSTANTS_INTERFACE.append("// Key Mappings\n");
		CONSTANTS_INTERFACE.append(CONSTANTS_KEY_MAPPING_CODES);
		for(int i = 0;i < m_KeyMappings.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)").append(m_KeyMappings.getKey(i));
		}
		CONSTANTS_INTERFACE.append(";\n");
		CONSTANTS_INTERFACE.append(CONSTANTS_KEY_MAPPING_ACTIONS);
		for(int i = 0;i < m_KeyMappings.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)").append(CONSTANTS_KEY_MAPPING_ACTION_PREFIX).append(((String)m_KeyMappings.getValue(i)).toUpperCase());
		}
		CONSTANTS_INTERFACE.append(";\n");

		CONSTANTS_INTERFACE.append("// Device Attributes\n");
		String strWidth = (String)deviceProperties.get("device.width");
		String strHeight = (String)deviceProperties.get("device.height");
		CONSTANTS_INTERFACE.append("    static final int DEVICE_WIDTH = ").append((strWidth == null) ? "-1" : strWidth).append(";\n");
		CONSTANTS_INTERFACE.append("    static final int DEVICE_HEIGHT = ").append((strHeight == null) ? "-1" : strHeight).append(";\n");
		
		String strAudioVolume = (String)deviceProperties.get("device.audioVolume");
		CONSTANTS_INTERFACE.append("    static final int DEVICE_AUDIO_VOLUME = ").append((strAudioVolume == null) ? "-1" : strAudioVolume).append(";\n");

		// end of file
		CONSTANTS_INTERFACE.append("}");
		out.write(CONSTANTS_INTERFACE.toString().getBytes());
	}
	
	/**
	 * @ian tierney
	 * essentially does the same as writeConstantsInterface() but in CPP format
	 * Writes all the ids of the different resources as CPP file for brew.
	 * @param out
	 * @throws IOException
	 */
	public void writeBrewConstants(String name, OutputStream out, Properties deviceProperties) throws Exception
	{
		File headerFile = new File("src/Constants.h");
		FileOutputStream headerStream = new FileOutputStream(headerFile);
		
		StringBuffer headerStrBuff = new StringBuffer(5120);
		headerStrBuff.append(CONSTANTS_BREW_PREFIX.replaceAll("%NAME%", name));
		headerStrBuff.append("#ifndef __CONSTANTS_H__\n#define __CONSTANTS_H__\n");
		//#ifndef __CONSTANTS_H__
		//#define __CONSTANTS_H__
		
		CONSTANTS_INTERFACE.delete(0, CONSTANTS_INTERFACE.length());

		CONSTANTS_INTERFACE.append("#include \"Constants.h\" \n");
		CONSTANTS_INTERFACE.append("\n");

		CONSTANTS_INTERFACE.append(CONSTANTS_BREW_PREFIX.replaceAll("%NAME%", name));
		//CONSTANTS_INTERFACE.append(CONSTANTS_INTERFACE_PALETTE_FILE_SIZE + paletteFileSize + ";\n");

		CONSTANTS_INTERFACE.append("//Text IDs\n");
		for(int i = 0;i < textsMap.size();i++ )
		{
			CONSTANTS_INTERFACE.append("#define TEXT_ID_").append(GetFormalName(textsMap.getKey(i))).append('=').append(Integer.toString(i)).append(";\n");
		}
		CONSTANTS_INTERFACE.append("#define MAX_TEXT_ID = " + textsMap.size() + ";\n");
		CONSTANTS_INTERFACE.append("#define NUM_COMMON_TEXTS = " + (textsMap.size() - m_nGlobalTextCount) + ";\n");
		CONSTANTS_INTERFACE.append("#define NUM_GLOBAL_TEXTS = " + m_nGlobalTextCount + ";\n");
		
		/*//@ian leaving these out for the moment dont seem 2 b use in brew
		CONSTANTS_INTERFACE.append("    static final String TEXT_LANGUAGE_FILENAMES = \"\"");
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguage(nLanguage);
			CONSTANTS_INTERFACE.append(" +\n        (char)TEXT_ID_LANGUAGE_FILENAME_" + strLanguage.toUpperCase().replace('-', '_'));
		}
		CONSTANTS_INTERFACE.append(";\n");
		*/
		//CONSTANTS_INTERFACE.append("    static final String TEXT_LANGUAGE_NAMES = \"\"");
		CONSTANTS_INTERFACE.append("const int Constants::TEXT_LANGUAGE_NAMES[] = \n{\n\t");
		//write the constants struct
		headerStrBuff.append("struct Constants\n{\n");
		headerStrBuff.append("\tstatic const int TEXT_LANGUAGE_NAMES[" + textConfiguration.getLanguageCount() + "];\n");
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguageDescriptor(nLanguage);
			CONSTANTS_INTERFACE.append("TEXT_ID_" + strLanguage);
			if(nLanguage < (textConfiguration.getLanguageCount()-1) )
			{	
				CONSTANTS_INTERFACE.append(",");
			}
		}
		CONSTANTS_INTERFACE.append("\n};\n");//closes array declaration
		
		for(int nLanguage = 0;nLanguage < textConfiguration.getLanguageCount();nLanguage++ )
		{
			String strLanguage = textConfiguration.getLanguage(nLanguage);
			CONSTANTS_INTERFACE.append("#define LANGUAGE_" + strLanguage.toUpperCase().replace('-', '_') + " = " + nLanguage + ";\n");
		}
		CONSTANTS_INTERFACE.append("#define MAX_LANGUAGE = " + textConfiguration.getLanguageCount() + ";\n");

		CONSTANTS_INTERFACE.append("//Image IDs\n");
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append( "#define IMAGE_ID_").append(((ImageDescriptor)imageDescriptors.getValue(i)).getName().replace('.', '_').toUpperCase()).append(" = ").append(i).append(";\n");
		}
		CONSTANTS_INTERFACE.append("#define IMAGE_MAX_ID	").append(m_nImageCount).append(";\n");
		CONSTANTS_INTERFACE.append("#define	MAX_IMAGE_SIZE	").append(m_nMaxImageSize).append(";\n");

		writeConstantDefinitions(paletteIdMap, "#define PALETTE_ID_", "//Palette IDs");

		//the font ids
		CONSTANTS_INTERFACE.append("//Font Ids\n");
		for(int i = 0;i < m_FontIds.size();i++ )
		{
			CONSTANTS_INTERFACE.append("#define FONT_ID_").append(GetFormalName((String)m_FontIds.get(i))).append(" = ").append(i).append(";\n");
		}
		CONSTANTS_INTERFACE.append(ID_PREFIX_SOFT_KEYS_FONT_ID).append(m_nSoftKeysFont).append(";\n");

		// write sound index types
		CONSTANTS_INTERFACE.append("//Sound Index Types\n");
		CONSTANTS_INTERFACE.append("#define SOUND_INDEX_").append("NORMAL = " + SOUND_INDEX_NORMAL + ";\n");
		CONSTANTS_INTERFACE.append("#define SOUND_INDEX_").append("MIDI_STRIP = " + SOUND_INDEX_MIDI_STRIP + ";\n");
		CONSTANTS_INTERFACE.append("#define SOUND_INDEX_").append("RESOURCE = " + SOUND_INDEX_RESOURCE + ";\n");

		// write sound ids
		CONSTANTS_INTERFACE.append("//Sound IDs\n");
		for(int i = 0;i < soundMap.size();i++ )
		{
			SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
			CONSTANTS_INTERFACE.append("#define SOUND_ID_").append(GetFormalName(soundMap.getKey(i))).append('=').append(Integer.toString(i)).append(";\n");
		}
		CONSTANTS_INTERFACE.append("#define MAX_SOUND_ID").append(Integer.toString(soundMap.size())).append(";\n");

		/*
		//@ian not sure how to format these
		// write sound formats
		CONSTANTS_INTERFACE.append(CONSTANTS_SOUND_FORMAT);
		for(int i = 0;i < soundMap.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)");
			SoundDescriptor sound = (SoundDescriptor)soundMap.getValue(i);
			if(sound.m_strPath == null)
			{
				CONSTANTS_INTERFACE.append("-1");
			}
			else
			{
				CONSTANTS_INTERFACE.append(CONSTANT_PREFIX_AUDIO_FORMAT).append(GetFormalName(sound.m_strAudioFormat));
			}
		}
		CONSTANTS_INTERFACE.append(";\n");
		
		
		// write image formats
		CONSTANTS_INTERFACE.append(CONSTANTS_IMAGE_FORMAT);
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append(" +\n        (char)").append(CONSTANT_PREFIX_IMAGE_FORMAT).append(((ImageDescriptor)imageDescriptors.getValue(i)).getExtension().toUpperCase());
		}
		CONSTANTS_INTERFACE.append(";\n");
		*/
		
		// write image transforms
		CONSTANTS_INTERFACE.append("const int Constants::IMAGE_TRANSFORM[] = \n{");
		headerStrBuff.append("\tstatic const int IMAGE_TRANSFORM[" + imageDescriptors.size() + "];\n");
		for(int i = 0;i < imageDescriptors.size();i++ )
		{
			CONSTANTS_INTERFACE.append("\n\t").append(((ImageDescriptor)imageDescriptors.getValue(i)).getTransformation());
			if(i < imageDescriptors.size()-1)
			{	
				CONSTANTS_INTERFACE.append(",");
			}
		}
		CONSTANTS_INTERFACE.append("\n}; \n");//closes array declaration

		CONSTANTS_INTERFACE.append("//Explicit constants\n");
		
		//writeExplicitConstants();

		writeGameDataConstants();
		CONSTANTS_INTERFACE.append("//ID Sequences\n");
		writeSequencesConstants();

		CONSTANTS_INTERFACE.append("//Layouts\n");
		writeLayoutsConstants();

		CONSTANTS_INTERFACE.append("//Vectors\n");
		writeVectorsConstants();
		
		/* @ian removing for the moment not sure we need them for brew
		CONSTANTS_INTERFACE.append("//Image Set Ids\n");
		writeSetIds();
		*/
		
		headerStrBuff.append("\tstatic const int KEY_MAPPING_CODES[" + m_KeyMappings.size() + "];\n");
		CONSTANTS_INTERFACE.append("//Game Boards - for mindbender.\n");
		CONSTANTS_INTERFACE.append("// Key Mappings\n");
		CONSTANTS_INTERFACE.append("const int Constants::KEY_MAPPING_CODES[] = \n{\n");
		for(int i = 0;i < m_KeyMappings.size();i++ )
		{
			CONSTANTS_INTERFACE.append("\t").append(m_KeyMappings.getKey(i));
			if(i < m_KeyMappings.size()-1)
			{	
				CONSTANTS_INTERFACE.append(",");
			}
		}
		
		headerStrBuff.append("\tstatic const int KEY_MAPPING_ACTIONS[" + m_KeyMappings.size() + "];\n");
		CONSTANTS_INTERFACE.append("\n }; \n");//closes array declaration
		CONSTANTS_INTERFACE.append("const int Constants::KEY_MAPPING_ACTIONS[] = \n{");
		for(int i = 0;i < m_KeyMappings.size();i++ )
		{
			CONSTANTS_INTERFACE.append("\n\t").append("KEY_INDEX_").append(((String)m_KeyMappings.getValue(i)).toUpperCase());
			if(i < m_KeyMappings.size()-1)
			{	
				CONSTANTS_INTERFACE.append(",");
			}
		}
		CONSTANTS_INTERFACE.append("\n }; \n");//closes array declaration
		
		/*ian removed dont need this for brew
		CONSTANTS_INTERFACE.append("// Device Attributes\n");
		String strWidth = (String)deviceProperties.get("device.width");
		String strHeight = (String)deviceProperties.get("device.height");
		CONSTANTS_INTERFACE.append("    #define DEVICE_WIDTH = ").append((strWidth == null) ? "-1" : strWidth).append(";\n");
		CONSTANTS_INTERFACE.append("    #define DEVICE_HEIGHT = ").append((strHeight == null) ? "-1" : strHeight).append(";\n");
		
		String strAudioVolume = (String)deviceProperties.get("device.audioVolume");
		CONSTANTS_INTERFACE.append("    #define DEVICE_AUDIO_VOLUME = ").append((strAudioVolume == null) ? "-1" : strAudioVolume).append(";\n");

		// end of file
		CONSTANTS_INTERFACE.append("}");
		*/
		out.write(CONSTANTS_INTERFACE.toString().getBytes());	
		
		
		headerStrBuff.append("}\n");		
		headerStrBuff.append("\n#endif //end hash gaurds");
		headerStream.write(headerStrBuff.toString().getBytes());	
	}

	protected void writeExplicitConstants()
	{
		Iterator it = explicitConstants.iterator();
		while (it.hasNext())
		{
			String[] constant = (String[])it.next();
			CONSTANTS_INTERFACE.append(PREFIX_EXPLICIT_CONSTANT).append(constant[0].trim()).append(" ").append(constant[1].trim().replace('.', '_').toUpperCase()).append(" = ").append(constant[2]).append(";\n");
		}
	}

	protected void writeGameDataConstants()
	{
		//The inheriting classes have the chance to add logic here.
	}

	protected void writeSequencesConstants()
	{
		Iterator it = sequencesList.iterator();
		StringBuffer sequenceString = new StringBuffer();
		int count = 0;
		while (it.hasNext())
		{
			count = 0;
			Map sequence = (Map)it.next();

			sequenceString.delete(0, sequenceString.length());
			Iterator items = ((List)sequence.get("items")).iterator();
			String type = (String)sequence.get("type");
			if("BREW".equalsIgnoreCase(m_nTarget) == false)
			{
				sequenceString.append("\"");
			}

			Object map = null;
			if("texts".equals(type))
			{
				map = textsMap;
			}
			else if("images".equals(type))
			{
				map = imageDescriptors;
			}
			else if("sounds".equals(type))
			{
				map = soundMap;
			}
			else
			{
				map = getSequenceMap(type);
			}

			if(map != null)
			{
				if(map instanceof ArrayMap)
				{
					ArrayMap aMap = (ArrayMap)map;
					while (items.hasNext())
					{
						String item = (String)items.next();
						char itemId = (char)aMap.indexOf(item);
						sequenceString.append(idToHexChar(itemId));
						count++;
						if("BREW".equalsIgnoreCase(m_nTarget))
						{
							sequenceString.append(",");
						}
					}
				}
				else if(map instanceof Map)
				{
					Map aMap = (Map)map;
					while (items.hasNext())
					{
						String item = (String)items.next();
						char itemId = (char)((Integer)aMap.get(item)).intValue();
						sequenceString.append(idToHexChar(itemId));
						count++;
						if("BREW".equalsIgnoreCase(m_nTarget))
						{
							sequenceString.append(",");
						}
					}
				}
			}
			if("BREW".equalsIgnoreCase(m_nTarget))
			{
				int index = sequenceString.lastIndexOf(",");
				sequenceString.deleteCharAt(index);
				PREFIX_SEQUENCE = "\tconst int Constants::SEQUENCE_";
				CONSTANTS_INTERFACE.append(PREFIX_SEQUENCE).append(((String)sequence.get("id")).trim().replace('.', '_').toUpperCase()).append("[").append(count).append("] = {").append(sequenceString).append("};\n");
					
			}
			else
			{
				sequenceString.append("\"");
				CONSTANTS_INTERFACE.append(PREFIX_SEQUENCE).append(((String)sequence.get("id")).trim().replace('.', '_').toUpperCase()).append(" = ").append(sequenceString).append(";\n");
			}
		}
	}

	/**
	 * PJK - just a modified writeSequencesConstants 
	 *
	 */
	protected void writeLayoutsConstants()
	{
		Iterator it = layoutsList.iterator();
		StringBuffer layoutString = new StringBuffer();
		int count = 0;
		while (it.hasNext())
		{
			count = 0;
			Map layout = (Map)it.next();

			layoutString.delete(0, layoutString.length());
			Iterator items = ((List)layout.get("items")).iterator();
			if("BREW".equalsIgnoreCase(m_nTarget) == false)
			{
				layoutString.append("\"");
			}

			if(imageDescriptors != null)
			{
				while (items.hasNext())
				{
					String item = (String)items.next();
					int x = Integer.parseInt((String)items.next());
					int y = Integer.parseInt((String)items.next());
					char itemId = (char)imageDescriptors.indexOf(item);
					layoutString.append(idToHexChar(itemId));
					count++;
					if("BREW".equalsIgnoreCase(m_nTarget))
					{
						layoutString.append(",");
					}
					layoutString.append(idToHexChar(x));
					count++;
					if("BREW".equalsIgnoreCase(m_nTarget))
					{
						layoutString.append(",");
					}
					layoutString.append(idToHexChar(y));
					count++;
					if("BREW".equalsIgnoreCase(m_nTarget))
					{
						layoutString.append(",");
					}
				}
			}
			if("BREW".equalsIgnoreCase(m_nTarget))
			{
				int index = layoutString.lastIndexOf(",");
				layoutString.deleteCharAt(index);
				PREFIX_LAYOUT = "\tconst int Constants::LAYOUT_";
				CONSTANTS_INTERFACE.append(PREFIX_LAYOUT).append(((String)layout.get("id")).trim().replace('.', '_').toUpperCase()).append("[").append(count).append("] = {").append(layoutString).append("};\n");
			}
			else
			{
				layoutString.append("\"");
				CONSTANTS_INTERFACE.append(PREFIX_LAYOUT).append(((String)layout.get("id")).trim().replace('.', '_').toUpperCase()).append(" = ").append(layoutString).append(";\n");
			}
		}
	}
	
	/**
	 * PJK - just a modified writeSetIds 
	 *
	 */
	protected void writeSetIds()
	{
		int i = 0;
		Iterator it = m_SetGroupIds.iterator();		
		while (it.hasNext())
		{	
			String[] temp = (String[])it.next();
			CONSTANTS_INTERFACE.append(PREFIX_IMAGE_SET_ID).append( temp[ 0 ] ).append(" = ").append( "(1<<" + temp[ 1 ] + ")" ).append(";\n");
			i++;
		}

	}

	/**
	 * PJK - just a modified writeSequencesConstants 
	 *
	 */
	protected void writeVectorsConstants()
	{
		if(vectorList == null)
		{
			return;
		}

		Iterator it = vectorList.iterator();
		StringBuffer vectorString = new StringBuffer();
		int count = 0;
		while (it.hasNext())
		{
			count = 0;
			Map vector = (Map)it.next();

			vectorString.delete(0, vectorString.length());
			Iterator items = ((List)vector.get("coords")).iterator();
			if("BREW".equalsIgnoreCase(m_nTarget) == false)
			{
				vectorString.append("\"");
			}

			if(items != null)
			{
				while (items.hasNext())
				{
					int x = Integer.parseInt((String)items.next());
					int y = Integer.parseInt((String)items.next());
					vectorString.append(idToHexChar(x));
					count++;
					if("BREW".equalsIgnoreCase(m_nTarget))
					{
						vectorString.append(",");
					}
					vectorString.append(idToHexChar(y));
					count++;
					if("BREW".equalsIgnoreCase(m_nTarget))
					{
						vectorString.append(",");
					}
				}
			}
			if("BREW".equalsIgnoreCase(m_nTarget))
			{
				int index = vectorString.lastIndexOf(",");
				vectorString.deleteCharAt(index);
				PREFIX_VECTOR = "\tconst int Constants::VECTOR_";
				CONSTANTS_INTERFACE.append(PREFIX_VECTOR).append(((String)vector.get("id")).trim().replace('.', '_').toUpperCase()).append("[").append(count).append("] = {").append(vectorString).append("};\n");
			}
			else
			{
				vectorString.append("\"");
				CONSTANTS_INTERFACE.append(PREFIX_VECTOR).append(((String)vector.get("id")).trim().replace('.', '_').toUpperCase()).append(" = ").append(vectorString).append(";\n");
			}
		}
	}

	public Object getSequenceMap(String string)
	{
		// TODO Auto-generated method stub
		return null;
	}

	static public String idToHexChar(int id)
	{
		String result = Integer.toHexString(id & 0xffff);
		if("BREW".equalsIgnoreCase(m_nTarget))
		{
			return String.valueOf(id);
		}
		else
		{
			switch (id)
			{
				case '\b':
					result = "\\b";
					break;
				case '\n':
					result = "\\n";
					break;
				case '\t':
					result = "\\t";
					break;
				case '\f':
					result = "\\f";
					break;
				case '\r':
					result = "\\r";
					break;
				case '\'':
					result = "\\'";
					break;
				case '"':
					result = "\\\"";
					break;
				case '\\':
					result = "\\\\";
					break;
				default:
					int size = result.length();
					if(size < 4)
					{
						for(int i = 0;i < 4 - size;i++ )
						{
							result = "0" + result;
						}
					}
					result = "\\u" + result;
					break;
			}
		}
		return result;
	}

	/**
	 * Writes the Java constants for a single type of resources.
	 * 
	 * @param idMap
	 * @param typePrefix
	 * @param typeComment
	 */
	protected static final void writeConstantDefinitions(Map idMap, String typePrefix, String typeComment)
	{
		if(idMap == null)
			return;
		CONSTANTS_INTERFACE.append(typeComment).append('\n');
		Iterator it = idMap.entrySet().iterator();
		while (it.hasNext())
		{
			Map.Entry element = (Map.Entry)it.next();
			CONSTANTS_INTERFACE.append(typePrefix).append(element.getKey().toString().trim().replace('.', '_').replace(' ', '_').replace('-', '_').toUpperCase()).append('=').append(((Integer)element.getValue()).intValue()).append(";\n");
		}
	}

	/**
	 * Utility method for extracting an id from an idMap.
	 * @param strId
	 * @param idMap
	 * @return
	 */
	protected int getId(String strId, Map idMap)
	{
		if(strId != null)
		{
			Integer id = (Integer)idMap.get(strId);
			if(id != null)
			{
				return id.intValue();
			}
			else
			{
				throw new NullPointerException("Non existing id reference: " + strId);
			}
		}
		else
		{
			throw new NullPointerException("Null id value");
		}
	}

	/**
	 * Converts an ID name to a name suitable for a constant definition
	 * @param strName the name to convert
	 */
	public String GetFormalName(String strName)
	{
		return strName.trim().replace('.', '_').replace(' ', '_').replace('-', '_').toUpperCase();
	}

	public static void copyFile(String strOldName, String strNewName) throws IOException
	{
		InputStream in = new FileInputStream(new File(strOldName));
		File outFile = new File(strNewName);
		outFile.delete();
		outFile.createNewFile();
		OutputStream out = new FileOutputStream(outFile);

		// Transfer bytes from in to out
		byte[] buf = new byte[1024];
		int len;
		while ((len = in.read(buf)) > 0)
		{
			out.write(buf, 0, len);
		}
		in.close();
		out.close();
	}

	public static BufferedReader executeApp(String strPath, String strCommand, String[] params, boolean ignoreOutput) throws IOException
	{
		// Array commands and parameters in a proper array
		int nParams = 0;
		if(params != null)
		{
			nParams = params.length;
		}
		String[] cmdArray = new String[nParams + 1];
		cmdArray[0] = strPath + strCommand;
		for(int i = 0;i < nParams;i++ )
		{
			cmdArray[i + 1] = params[i];
		}

		Process process = null;
		try
		{
			process = Runtime.getRuntime().exec(cmdArray);
			BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()));

			String line = err.readLine();
			boolean bError = line != null && !ignoreOutput;

			if(bError)
			{
				System.out.println("[*] ERROR executing " + strCommand.toUpperCase() + ": ");
			}
			while (line != null)
			{
				if(line != null)
				{
					System.err.println(line);
					line = err.readLine();
				}
			}

			int exitValue = process.waitFor();
			if(exitValue != 0 || bError)
			{
				String strParams = "";
				for(int i = 0;i < params.length;i++ )
				{
					strParams += " ";
					strParams += params[i];
				}
				throw new Exception("Execute " + strCommand.toUpperCase() + strParams + " (" + exitValue + ")");
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}

		if(process != null)
		{
			return new BufferedReader(new InputStreamReader(process.getInputStream()));
		}
		else
		{
			return null;
		}
	}
}

/**
 * 
 * ImageDescriptor 
 *
 * Description:
 * A datastructure representing an image and all its transforms.
 * <br/> 
 *
 * @author Plamen Dragozov plamen.dragozov@tko-software.com
 *
 */

class ImageDescriptor
{
	private String m_strName;
	private String m_strPath;
	private ImageDescriptor m_imageBase = null;
	private boolean m_bHFlip = false;
	private boolean m_bVFlip = false;
	private boolean m_bRot90 = false;
	private int m_nPalette = -1;
	private int m_nSets = 0;
	private int m_nChildSets = 0;
	private boolean m_bFlippedChildren = false;

	public ImageDescriptor(String strName, String strPath, ArrayList sets)
	{
		m_strName = strName;
		m_strPath = strPath.trim();
		ParseSets(sets);
	}

	public ImageDescriptor(ImageDescriptor imageBase, String strName, ArrayList sets, boolean bHFlip, boolean bVFlip, boolean bRot90, int nPalette)
	{
		m_imageBase = imageBase;
		m_strName = strName;
		m_strPath = imageBase.m_strPath.trim();
		m_bHFlip = bHFlip;
		m_bVFlip = bVFlip;
		m_bRot90 = bRot90;
		m_nPalette = nPalette;
		if(bHFlip || bVFlip)
		{
			m_imageBase.m_bFlippedChildren = true;
		}
		ParseSets(sets);
		m_imageBase.m_nChildSets |= m_nSets; // apply sets to parent
	}

	public void addSets(int nSets)
	{
		m_nSets |= nSets;
		if(m_imageBase != null)
		{
			m_imageBase.m_nChildSets |= nSets;
		}
	}

	public int getSets()
	{
		return m_nSets;
	}

	public int getChildSets()
	{
		return m_nChildSets;
	}

	public int getTransformation()
	{
		int nTransform = 0;
		if(m_bHFlip)
		{
			nTransform |= GameDescriptorHandler.IMAGE_TRANSFORM_FLIP_HORIZONTAL;
		}
		if(m_bVFlip)
		{
			nTransform |= GameDescriptorHandler.IMAGE_TRANSFORM_FLIP_VERTICAL;
		}
		if(m_bRot90)
		{
			nTransform |= GameDescriptorHandler.IMAGE_TRANSFORM_ROTATE_90;
		}
		if(m_nPalette != -1)
		{
			nTransform |= GameDescriptorHandler.IMAGE_TRANSFORM_PALETTE_SHIFT;
			nTransform |= m_nPalette << 8;
		}
		return nTransform;
	}

	private void ParseSets(ArrayList sets)
	{
		m_nSets = 0;
		if(sets != null && sets.size() > 0)
		{
			for(int i = 0;i < sets.size();i++ )
			{
				m_nSets |= 1 << Integer.parseInt((String)sets.get(i));
			}
		}
		else
		{
			m_nSets = 1;
		}
	}

	public boolean isTransform()
	{
		return m_imageBase != null;
	}

	public boolean hasFlippedChildren()
	{
		return m_bFlippedChildren;
	}

	public String getName()
	{
		return m_strName;
	}

	public String getPath()
	{
		return m_strPath;
	}

	public String getExtension()
	{
		return m_strPath.substring(m_strPath.indexOf(".") + 1);
	}
}

/**
 * 
 * SoundDescriptor 
 *
 * Description:
 * A datastructure representing an sound and all its data.
 * <br/> 
 *
 * @author Gabriel Littman gabriel.littman@tko-software.com
 *
 */
class SoundDescriptor
{
	int m_nSet = 0; //the setID describes when the sound should be loaded
	String m_strAudioFormat = null; // name of audio format
	String m_strPath = null; // path of audio file
	int m_nDataIndex = -1; // index in soundDataMap which represents the sound's data
	int m_nStartPos = -1;
	int m_nEndPos = -1;

	SoundDescriptor()
	{
	}

	void setAudioFormat(String strAudioFormat)
	{
		m_strAudioFormat = strAudioFormat;
	}

	void setPath(String strPath)
	{
		// translate start/endpos if present in path
		int nFirstAdjustment = strPath.indexOf(":");
		if(nFirstAdjustment != -1)
		{
			int nSecondAdjustment = strPath.indexOf(":", nFirstAdjustment + 1);
			int nPos = Integer.parseInt(strPath.substring(nFirstAdjustment + 1, nSecondAdjustment)) * 10000;
			int nLength = Integer.parseInt(strPath.substring(nSecondAdjustment + 1)) * 10000;
			setStartPos(nPos);
			setEndPos(nPos + nLength);
			m_strPath = strPath.substring(0, nFirstAdjustment);
		}
		else
		{
			m_strPath = strPath;
		}
	}

	void setDataIndex(int nIndex)
	{
		m_nDataIndex = nIndex;
	}

	void setStartPos(int nMicroseconds)
	{
		m_nStartPos = nMicroseconds;
	}

	void setEndPos(int nMicroseconds)
	{
		m_nEndPos = nMicroseconds;
	}

	void addSet(int nSet)
	{
		m_nSet |= (1 << nSet);
	}

	String getAudioFormat()
	{
		return m_strAudioFormat;
	}

	String getPath()
	{
		return m_strPath;
	}

	int getDataIndex()
	{
		return m_nDataIndex;
	}

	int getStartPos()
	{
		return m_nStartPos;
	}

	int getEndPos()
	{
		return m_nEndPos;
	}
}

/**
 * 
 * SoundTypeDescriptor 
 *
 * Description:
 * A datastructure representing a sound properties file
 *
 * @author Evan Salomon evan.salomon@tko-software.com
 *
 */
class SoundTypeDescriptor
{
	private String m_strDevice; // name of device
	private String m_strFormat; // name of audio format
	private String m_strPath; // path of properties file

	SoundTypeDescriptor(String strDevice, String strFormat, String strPath)
	{
		m_strDevice = strDevice;
		m_strFormat = strFormat;
		m_strPath = strPath;
	}

	String getDevice()
	{
		return m_strDevice;
	}

	String getFormat()
	{
		return m_strFormat;
	}

	String getPath()
	{
		return m_strPath;
	}
}

/**
 * 
 * AudioFormatDescriptor 
 *
 * Description:
 * A datastructure representing attributes of a sound format
 *
 * @author Evan Salomon evan.salomon@tko-software.com
 *
 */
class AudioFormatDescriptor
{
	private boolean m_bStrip = false; // indicated if this format uses a strip

	AudioFormatDescriptor()
	{
	}

	void setStrip(boolean bStrip)
	{
		m_bStrip = bStrip;
	}

	boolean isStrip()
	{
		return m_bStrip;
	}
}

/**
 * 
 * TextConfigurationDescriptor 
 *
 * Description:
 * A datastructure representing attributes of a sound format
 *
 * @author Evan Salomon evan.salomon@tko-software.com
 *
 */
class TextConfigurationDescriptor
{
	private ArrayMap m_Languages = new ArrayMap();
	private String m_strGlobalLanguage;
	private ArrayMap m_Orientations = new ArrayMap();

	TextConfigurationDescriptor(String strGlobalLanguage)
	{
		m_strGlobalLanguage = strGlobalLanguage;
	}

	void addLanguage(String strLanguage, String strDescriptor)
	{
		m_Languages.put(strLanguage, strDescriptor);
	}

	void setLanguageOrientation(int nLanguage, String strOrientation)
	{
		m_Orientations.put(getLanguage(nLanguage), strOrientation);
	}

	String getGlobalLanguage()
	{
		return m_strGlobalLanguage;
	}

	int getLanguageCount()
	{
		return m_Languages.size();
	}

	String getLanguage(int nLanguage)
	{
		if(nLanguage == -1)
		{
			return m_strGlobalLanguage;
		}
		else
		{
			return (String)m_Languages.getKey(nLanguage);
		}
	}

	String getLanguageDescriptor(int nLanguage)
	{
		return (String)m_Languages.getValue(nLanguage);
	}
	
	String getLanguageOrientation(int nLanguage)
	{
		return (String)m_Orientations.get(getLanguage(nLanguage));
	}

}
